SoundHound Programming Contest 2018 Masters Tournament 本戦 (Open) E - Hash Swapping
解法
セグメント木の子ノード j に を入れておき、合計値を求める際に の逆元をかけることでハッシュを取り出せるようにする。各ノードは左右の子ノードへのポインタ(ポインタ操作は Rust で安全にやろうとすると重くなるので、実際には配列のインデックス)をそれぞれ持っていて、更新範囲内に含まれていたらポインタを張り替えることで swap クエリを処理する。
コード
const EXP: usize = 1e6 as usize; const MOD: usize = 1e9 as usize + 7; #[derive(Clone, Debug)] struct Node { left: usize, right: usize, from: usize, to: usize, sum: usize, } struct SegTree { nodes: Vec<Node>, total: usize, } impl SegTree { fn new(n: usize, m: usize) -> Self { let mut size = 1; while size <= n { size *= 2; } let total = size * 2; let mut seg = vec![ Node { left: 0, right: 0, from: 0, to: 0, sum: 0 }; total * m ]; for m in 0..m { let offset = total * m; init_ptr(&mut seg, 0, offset, 0, size - 1); } SegTree { nodes: seg, total: total, } } fn update(&mut self, pos: usize, value: usize, i: usize) { if self.nodes[pos].from == self.nodes[pos].to && self.nodes[pos].from == i { self.nodes[pos].sum = value; } else if self.nodes[pos].from <= i && i <= self.nodes[pos].to { let left = self.nodes[pos].left; let right = self.nodes[pos].right; self.update(left, value, i); self.update(right, value, i); self.nodes[pos].sum = (self.nodes[left].sum + self.nodes[right].sum) % MOD; } } fn sum(&self, from: usize, to: usize, pos: usize) -> usize { if to < self.nodes[pos].from || self.nodes[pos].to < from { 0 } else if from <= self.nodes[pos].from && self.nodes[pos].to <= to { self.nodes[pos].sum } else { let left = self.sum(from, to, self.nodes[pos].left); let right = self.sum(from, to, self.nodes[pos].right); (left + right) % MOD } } fn swap(&mut self, pos1: usize, pos2: usize, from: usize, to: usize) { let ptr1 = self.nodes[pos1].left; let ptr2 = self.nodes[pos2].left; if from <= self.nodes[ptr1].from && self.nodes[ptr1].to <= to { self.nodes[pos1].left = ptr2; self.nodes[pos2].left = ptr1; } else if !(to < self.nodes[ptr1].from || self.nodes[ptr1].to < from) { self.swap(ptr1, ptr2, from, to); } let ptr1 = self.nodes[pos1].right; let ptr2 = self.nodes[pos2].right; if from <= self.nodes[ptr1].from && self.nodes[ptr1].to <= to { self.nodes[pos1].right = ptr2; self.nodes[pos2].right = ptr1; } else if !(to < self.nodes[ptr1].from || self.nodes[ptr1].to < from) { self.swap(ptr1, ptr2, from, to); } let mut update = |pos: usize| { let left = self.nodes[pos].left; let right = self.nodes[pos].right; self.nodes[pos].sum = (self.nodes[left].sum + self.nodes[right].sum) % MOD; }; update(pos1); update(pos2); } } fn init_ptr(seg: &mut Vec<Node>, k: usize, offset: usize, from: usize, to: usize) { seg[k + offset].from = from; seg[k + offset].to = to; if from != to { let width = to + 1 - from; assert_eq!(width & 1, 0); seg[k + offset].left = 2 * k + 1 + offset; seg[k + offset].right = 2 * k + 2 + offset; init_ptr(seg, 2 * k + 1, offset, from, from + width / 2 - 1); init_ptr(seg, 2 * k + 2, offset, from + width / 2, to); } } fn solve(n: usize, s: &Vec<Vec<usize>>, q: &Vec<(usize, usize, usize, usize, usize)>) { let m = s.len(); let mut mod_pow = vec![0; n + 1]; mod_pow[0] = 1; mod_pow[1] = EXP; let mut mod_inv = vec![0; n + 1]; mod_inv[0] = 1; mod_inv[1] = mod_inverse(EXP, MOD); for i in 2..n { mod_pow[i] = (mod_pow[i - 1] * mod_pow[1]) % MOD; mod_inv[i] = (mod_inv[i - 1] * mod_inv[1]) % MOD; } let mut seg = SegTree::new(n, m); for m in 0..m { for i in 0..n { let pos = m * seg.total; seg.update(pos, (s[m][i] * mod_pow[i]) % MOD, i); } } for &(t, x, y, from, to) in q.iter() { if t == 1 { let pos1 = x * seg.total; let pos2 = y * seg.total; seg.swap(pos1, pos2, from, to); } else { let pos = x * seg.total; let sum = seg.sum(from, to, pos); println!("{}", (sum * mod_inv[from]) % MOD); } } } fn main() { let mut sc = Scanner::new(); let n = sc.read(); let m = sc.read(); let s: Vec<Vec<usize>> = (0..m) .map(|_| { sc.read::<String>() .chars() .map(|c| c as usize - 'a' as usize + 1) .collect() }) .collect(); let q = sc.read(); let q = (0..q) .map(|_| { let t = sc.read(); if t == 1 { ( t, sc.usize_read() - 1, sc.usize_read() - 1, sc.usize_read() - 1, sc.usize_read() - 1, ) } else { ( t, sc.usize_read() - 1, sc.usize_read(), sc.usize_read() - 1, sc.usize_read() - 1, ) } }) .collect(); solve(n, &s, &q); } fn extended_gcd(a: i64, b: i64) -> (i64, i64, i64) { assert!(a < b); if a == 0 { (b, 0, 1) } else { let (g, x, y) = extended_gcd(b % a, a); (g, y - (b / a) * x, x) } } fn mod_inverse(a: usize, modulo: usize) -> usize { let (g, x, _) = extended_gcd(a as i64, modulo as i64); assert!(g == 1); (x as usize) % modulo } struct Scanner { ptr: usize, length: usize, buf: Vec<u8>, small_cache: Vec<u8>, } #[allow(dead_code)] impl Scanner { fn new() -> Scanner { Scanner { ptr: 0, length: 0, buf: vec![0; 1024], small_cache: vec![0; 1024], } } fn load(&mut self) { use std::io::Read; let mut s = std::io::stdin(); self.length = s.read(&mut self.buf).unwrap(); } fn byte(&mut self) -> u8 { if self.ptr >= self.length { self.ptr = 0; self.load(); if self.length == 0 { self.buf[0] = b'\n'; self.length = 1; } } self.ptr += 1; return self.buf[self.ptr - 1]; } fn is_space(b: u8) -> bool { b == b'\n' || b == b'\r' || b == b'\t' || b == b' ' } fn read_vec<T>(&mut self, n: usize) -> Vec<T> where T: std::str::FromStr, T::Err: std::fmt::Debug, { (0..n).map(|_| self.read()).collect() } fn usize_read(&mut self) -> usize { self.read() } fn read<T>(&mut self) -> T where T: std::str::FromStr, T::Err: std::fmt::Debug, { let mut b = self.byte(); while Scanner::is_space(b) { b = self.byte(); } for pos in 0..self.small_cache.len() { self.small_cache[pos] = b; b = self.byte(); if Scanner::is_space(b) { return String::from_utf8_lossy(&self.small_cache[0..(pos + 1)]) .parse() .unwrap(); } } let mut v = self.small_cache.clone(); while !Scanner::is_space(b) { v.push(b); b = self.byte(); } return String::from_utf8_lossy(&v).parse().unwrap(); } }
Rust でライブコーディングをした
これの LT 枠でライブコーディングをした。
動機
Rust は比較的難しい言語だと思う(ふわっと書いてふわっと動くタイプの言語ではない)ので、聴衆が自分でもできそうと思えるようなパフォーマンスをしたかった。
問題選定
Rust の標準ライブラリの公式ドキュメントにはダイクストラの実装が載っているので、これをコピペするだけで動くのを見せたいと考えた。 AOJ にはダイクストラをそのまま実装するための問題が用意されているが、それではあまりに味気ないので、本質的にはダイクストラをやるだけだが表面的には面白そうに見える問題を頑張って探した。
(探しても見つからなかったので、結局自分の印象に残っていた問題を思い出した)(僕が初めてダイクストラ法と出会った問題)
練習
本番前に練習をし、いくつかの問題点を見つけた。
- この問題では全ての頂点への最短距離を求める必要があるが、公式ドキュメントのものは終点までの最短経路のみを求めるので、微修正する必要がある。
- AtCoder のジャッジの Rust は 1.15.1 なので、そのままコピペすると
then_with
でコンパイルエラーになる。 - Edge が Clone を継承する必要がある。
- さり気なく
[#derive(Clone)]
を足す。
- さり気なく
- 標準入力が面倒。
- グラフが連結でないケースがテストに入っている。
本番
kenkooさんだ #rust_jp
— κeen (@blackenedgold) August 1, 2018
— Tsuzu (@Wp120_3238) 2018年8月1日
「どうせ自分の発表時間なくなるだろと思ってたので…」
— κeen (@blackenedgold) 2018年8月1日
#rust_jp
立川シネマシティの宣伝がwww #rust_jp
— Satoshi Kojima (@skoji) 2018年8月1日
— 電波椅子🐱 (@dempacat) 2018年8月1日
kenkooさんだ!!#rust_jp
— hello (@yomicerisier) 2018年8月1日
— hsjoihs@数情物化語 (@hsjoihs) 2018年8月1日
— おおた (@ota42y) 2018年8月1日
— おおた (@ota42y) 2018年8月1日
— えまのん (@emanon_was) 2018年8月1日
「今日家に帰ったら使える話をします」
— κeen (@blackenedgold) 2018年8月1日
#rust_jp
— なっぱ@パーフェクトドブマスター (@b7472) 2018年8月1日
「今すぐ5分以内にここでRustを使う話をします!!」 #rust_jp
— Satoshi Kojima (@skoji) 2018年8月1日
「スライドはない」「5分以内に使うRustを話します」 #rust_jp
— tjmmm (@norikakip) 2018年8月1日
— yuki (@helloyuk13) 2018年8月1日
— κeen (@blackenedgold) 2018年8月1日
「webアプリとか作るだけならPythonでいいんですよ!!」 #rust_jp
— Satoshi Kojima (@skoji) 2018年8月1日
— Tsuzu (@Wp120_3238) 2018年8月1日
atcoderの問題をライブコーディングして解くwwww #rust_jp
— おおた (@ota42y) 2018年8月1日
— 名有りさん (@naari_) 2018年8月1日
— κeen (@blackenedgold) 2018年8月1日
LTでAtCoderのC問題をライブコーディング。新しい #rust_jp
— 電波椅子🐱 (@dempacat) 2018年8月1日
ちゃんと時間は確保しますよ😅 #rust_jp
— どらやき (@dorayaki_kun) 2018年8月1日
「だいくすとら」を #rust_jp
— 目指せ黄色 (@matsu7874) 2018年8月1日
AtCoderのC問題をライブコーディングwww #rust_jp
— なっぱ@パーフェクトドブマスター (@b7472) 2018年8月1日
最強最速アルゴリズマー養成講座 プログラミングコンテストTopCoder攻略ガイド | 高橋 直大 |本 | 通販 | Amazonhttps://t.co/EOUsiDiXa7#rust_jp
— κeen (@blackenedgold) 2018年8月1日
Atcoderのライブコーディングは面白すぎる#rust_jp
— hello (@yomicerisier) 2018年8月1日
標準入力の受け取りめんどいよねw #rust_jp
— yuki (@helloyuk13) 2018年8月1日
ダイクストラ弊社社員うるさいです。僕はうるさくないですが。 #rust_jp
— どらやき (@dorayaki_kun) 2018年8月1日
マイクOFF!! #rust_jp
— なっぱ@パーフェクトドブマスター (@b7472) 2018年8月1日
標準入力を受け取るのもコピペでやっていくことにした
「標準入力受け取るのはRustだとめんどくさいのでコピペします!!」 #rust_jp 勢いがある
— Satoshi Kojima (@skoji) 2018年8月1日
「標準入力を受け取るのがRustは面倒なのでコピペでいきます」 #rust_jp
— 電波椅子🐱 (@dempacat) 2018年8月1日
AtCoderライブコーディングが始まったw #rust_jp
— Tsuzu (@Wp120_3238) 2018年8月1日
ガルパンの宣伝からガチライブコーディングw テンションぱねーw #rust_jp
— えまのん (@emanon_was) 2018年8月1日
「みなさんも実装してください僕といっしょに!!!」 #rust_jp
— tjmmm (@norikakip) 2018年8月1日
「みなさん、今、僕と一緒に、実装してください!!!」 #rust_jp
— Satoshi Kojima (@skoji) 2018年8月1日
喋りながらコード書ける技術何気にすごいと思う #rust_jp
— Yuli Inoue (@iyunoriue) 2018年8月1日
「皆さん、今、実装してくださいね?!僕といっしょに!!」
— κeen (@blackenedgold) 2018年8月1日
#rust_jp
「みなさんも一緒に実装してくださいね!!!」 #rust_jp
— なっぱ@パーフェクトドブマスター (@b7472) 2018年8月1日
「ダイクストラは実装しやすいんですが、ここはコピペでいきます」
— κeen (@blackenedgold) 2018年8月1日
#rust_jp
Rust公式ページにダイクストラが乗っている #rust_jp
— u+ (@uplus_e10) 2018年8月1日
「ダイクストラ法の実装はRustのbinary_heapのドキュメントに乗っている。それをそのままコピペします!!!」 wwwwww #rust_jp
— Satoshi Kojima (@skoji) 2018年8月1日
rustの公式ドキュメントにヒープを使ったダイクストラ法の実装があるwwwwwwwww #rust_jp
— おおた (@ota42y) 2018年8月1日
「RustのBinaryHeapのドキュメントにダイクストラ法のコードが乗っているのでコピペします」 #rust_jp
— 電波椅子🐱 (@dempacat) 2018年8月1日
「Rustはなんと標準ライブラリのBinaryHeapのドキュメントにダイクストラの実装が載ってます!!!これをコピペして使います!!!!」
— κeen (@blackenedgold) 2018年8月1日
#rust_jp
コピペによるライブコーディングが始まった #rust_jp
— soebosi (@ebosi38051) 2018年8月1日
— Satoshi Kojima (@skoji) 2018年8月1日
迷いなく書いてるあたり手慣れてるな〜
— κeen (@blackenedgold) 2018年8月1日
#rust_jp
— yuki (@helloyuk13) 2018年8月1日
— 電波椅子🐱 (@dempacat) 2018年8月1日
話しながら書くの難しそう #rust_jp
— mizdra (@mizdra) 2018年8月1日
公式docsにダイクストラの実装サンプルあるのかhttps://t.co/OTUeX1Jimc
— わさん (@wasanx25) 2018年8月1日
#rust_jp
「提出していきましょう」 #rust_jp
— なっぱ@パーフェクトドブマスター (@b7472) 2018年8月1日
ちゃんとコンパイルエラーになるところも見せた
AtCoderのRust古くなかったっけ・・・ #rust_jp
— Tsuzu (@Wp120_3238) 2018年8月1日
「AtCoderのRustのパージョンが古い!!」
— κeen (@blackenedgold) 2018年8月1日
#rust_jp
みっちり練習した痕跡よww #rust_jp
— えまのん (@emanon_was) 2018年8月1日
— なっぱ@パーフェクトドブマスター (@b7472) 2018年8月1日
AtCoderのRustコンパイラのバージョンが古いので、コピペしたところが動かない #rust_jp
— 電波椅子🐱 (@dempacat) 2018年8月1日
コーナーケースで不正解になるところも見せた
これはあまり伝わっていなかったかもしれない。
Rustの話じゃなくて問題の解説始まった #rust_jp
— 目指せ黄色 (@matsu7874) 2018年8月1日
「結局何をやったかというと、1行目〜84行目までコピペ」 #rust_jp
— 電波椅子🐱 (@dempacat) 2018年8月1日
「ほぼコピペなので自分で書いたコードはこれだけなんですね。なんかできそう!」
— κeen (@blackenedgold) 2018年8月1日
#rust_jp
「速さを享受できるアルゴリズムの問題を解くのがいいのでは」 #rust_jp
— 名有りさん (@naari_) 2018年8月1日
コンパイラのバージョンが古くてストレスが溜まったりしませんか #rust_jp
— Tsuzu (@Wp120_3238) 2018年8月1日
実装する方法は得てないぞwwww #rust_jp
— 電波椅子🐱 (@dempacat) 2018年8月1日
喋りながらのライブコーディングも良いし、まとめもとても良かったw #rust_jp
— わさん (@wasanx25) 2018年8月1日
「みなさんは今日帰ったらダイクストラを実装できます。」
— κeen (@blackenedgold) 2018年8月1日
#rust_jp
けんこーさんうまいなぁ #rust_jp
— Tsuzu (@Wp120_3238) 2018年8月1日
疾走感ある発表だった #rust_jp
— なっぱ@パーフェクトドブマスター (@b7472) 2018年8月1日
プロ #rust_jp
— Ryota Sakamoto (@ryota_sakamot0) 2018年8月1日
ライブ・コーディング面白かった #rust_jp
— Yuli Inoue (@iyunoriue) 2018年8月1日
話しながら実装してミスしないのすごかった… #rust_jp
— おおた (@ota42y) 2018年8月1日
感想
自己顕示欲の満たされ方がすごい。
Rust で競技プログラミングをする時に使う高速な標準入力 Scanner
struct Scanner { ptr: usize, length: usize, buf: Vec<u8>, small_cache: Vec<u8>, } #[allow(dead_code)] impl Scanner { fn new() -> Scanner { Scanner { ptr: 0, length: 0, buf: vec![0; 1024], small_cache: vec![0; 1024], } } fn load(&mut self) { use std::io::Read; let mut s = std::io::stdin(); self.length = s.read(&mut self.buf).unwrap(); } fn byte(&mut self) -> u8 { if self.ptr >= self.length { self.ptr = 0; self.load(); if self.length == 0 { self.buf[0] = b'\n'; self.length = 1; } } self.ptr += 1; return self.buf[self.ptr - 1]; } fn is_space(b: u8) -> bool { b == b'\n' || b == b'\r' || b == b'\t' || b == b' ' } fn read_vec<T>(&mut self, n: usize) -> Vec<T> where T: std::str::FromStr, T::Err: std::fmt::Debug, { (0..n).map(|_| self.read()).collect() } fn usize_read(&mut self) -> usize { self.read() } fn read<T>(&mut self) -> T where T: std::str::FromStr, T::Err: std::fmt::Debug, { let mut b = self.byte(); while Scanner::is_space(b) { b = self.byte(); } for pos in 0..self.small_cache.len() { self.small_cache[pos] = b; b = self.byte(); if Scanner::is_space(b) { return String::from_utf8_lossy(&self.small_cache[0..(pos + 1)]) .parse() .unwrap(); } } let mut v = self.small_cache.clone(); while !Scanner::is_space(b) { v.push(b); b = self.byte(); } return String::from_utf8_lossy(&v).parse().unwrap(); } }
同じディレクトリに複数の CMakeLists.txt を置きたい時
. ├── CMakeLists.txt # Root.cmake を include するだけ ├── main.cpp # main() ├── Mods # │ ├── Mod1.cmake # mod1.cpp => mod1 にする │ ├── mod1.cpp # │ ├── mod1.h # │ ├── Mod2.cmake # mod2.cpp => mod2 にする。実は Mod1 に依存している。 │ ├── mod2.cpp # │ └── mod2.h # └── Root.cmake # mod1 と mod2 をビルドして ./a.out を固める
AGC 003 D - Anticube
解説
各 s_i を素因数分解する(方法は後で考える)。
まず、 s_i に 3 つ同じ素因数が含まれるとき、それらを取り除いてしまっても構わない。例えば のとき、 を取り除いて と考えて良い。このように各 s_i を素数の 3 乗で割れるだけ割っておく。すると、各 s_i は と表せる。ここで前者を p_i 後者を q_i として とする。この s_i との積が立法数になる s_j は と表せるはずである。
なので、問題は と の数をそれぞれ求め、多い方を採用するようにする。また、 と表せるものは 1 つだけ採用できる。
最初に戻って s_i を素因数分解する方法を考える。 となるような全ての素数 p について調べたあとの s_i は素数の 3 乗を含まないので、以下の 3 つのいずれかである。
素数の2乗のとき、 と表せる。そうでない時は と表せる。このレベルまで分かれば十分なので、完全に素因数分解する必要はなく、実行時間制限に間に合う。
コード
use std::cmp; use std::collections::{BTreeMap, BTreeSet}; fn get_primes(n: usize) -> Vec<u64> { let mut is_prime = vec![true; n + 1]; is_prime[0] = false; is_prime[1] = false; for p in 2..(n + 1) { if !is_prime[p] { continue; } let mut cur = 2 * p; while cur <= n { is_prime[cur] = false; cur += p; } } is_prime .iter() .enumerate() .filter(|&(_, &is_prime)| is_prime) .map(|(p, _)| p as u64) .collect() } fn main() { let primes = get_primes(100000); let prime2 = primes .iter() .map(|&p| (p * p, p)) .collect::<BTreeMap<u64, u64>>(); let mut sc = Scanner::new(); let n = sc.usize_read(); let mut count = BTreeMap::new(); for _ in 0..n { let mut s: u64 = sc.read(); let mut s1 = 1; let mut s2 = 1; for &prime in &primes { let p2 = prime * prime; let p3 = p2 * prime; if p3 > s { break; } while s % p3 == 0 { s /= p3; } if s % p2 == 0 { s /= p2; s2 *= prime; } else if s % prime == 0 { s /= prime; s1 *= prime; } } match prime2.get(&s) { Some(&p) => s2 *= p, None => s1 *= s, } let &cur = count.get(&(s1, s2)).unwrap_or(&0); count.insert((s1, s2), cur + 1); } let mut ans = 0; let mut set = BTreeSet::new(); set.insert((1, 1)); for &(s1, s2) in count.keys() { if set.contains(&(s1, s2)) { continue; } let &count1 = count.get(&(s1, s2)).unwrap_or(&0); let &count2 = count.get(&(s2, s1)).unwrap_or(&0); ans += cmp::max(count1, count2); set.insert((s1, s2)); set.insert((s2, s1)); } if count.contains_key(&(1, 1)) { ans += 1; } println!("{}", ans); } struct Scanner { ptr: usize, length: usize, buf: Vec<u8>, small_cache: Vec<u8>, } #[allow(dead_code)] impl Scanner { fn new() -> Scanner { Scanner { ptr: 0, length: 0, buf: vec![0; 1024], small_cache: vec![0; 1024], } } fn load(&mut self) { use std::io::Read; let mut s = std::io::stdin(); self.length = s.read(&mut self.buf).unwrap(); } fn byte(&mut self) -> u8 { if self.ptr >= self.length { self.ptr = 0; self.load(); if self.length == 0 { self.buf[0] = b'\n'; self.length = 1; } } self.ptr += 1; return self.buf[self.ptr - 1]; } fn is_space(b: u8) -> bool { b == b'\n' || b == b'\r' || b == b'\t' || b == b' ' } fn read_vec<T>(&mut self, n: usize) -> Vec<T> where T: std::str::FromStr, T::Err: std::fmt::Debug, { (0..n).map(|_| self.read()).collect() } fn usize_read(&mut self) -> usize { self.read() } fn read<T>(&mut self) -> T where T: std::str::FromStr, T::Err: std::fmt::Debug, { let mut b = self.byte(); while Scanner::is_space(b) { b = self.byte(); } for pos in 0..self.small_cache.len() { self.small_cache[pos] = b; b = self.byte(); if Scanner::is_space(b) { return String::from_utf8_lossy(&self.small_cache[0..(pos + 1)]) .parse() .unwrap(); } } let mut v = self.small_cache.clone(); while !Scanner::is_space(b) { v.push(b); b = self.byte(); } return String::from_utf8_lossy(&v).parse().unwrap(); } }
ARC 072 D - Alice&Brown
コード
use std::collections::{BTreeMap, BTreeSet}; fn main() { let mut sc = Scanner::new(); let x: u64 = sc.read(); let y: u64 = sc.read(); let (x, y) = if x > y { (y, x) } else { (x, y) }; println!("{}", if y - x <= 1 { "Brown" } else { "Alice" }); // exp(); } fn exp() { let mut map = BTreeMap::new(); map.insert((0, 0), 0); map.insert((0, 1), 0); map.insert((1, 1), 0); for i in 0..1000 { for j in i..1000 { grundy(i, j, &mut map); } } } fn grundy(x: usize, y: usize, map: &mut BTreeMap<(usize, usize), usize>) -> usize { let (x, y) = if x > y { (y, x) } else { (x, y) }; if map.contains_key(&(x, y)) { return map[&(x, y)]; } let mut grundy_nums = BTreeSet::new(); for x_to_y in 1..(x / 2 + 1) { let next_x = x - x_to_y * 2; let next_y = y + x_to_y; grundy_nums.insert(grundy(next_x, next_y, map)); } for y_to_x in 1..(y / 2 + 1) { let next_x = x + y_to_x; let next_y = y - y_to_x * 2; grundy_nums.insert(grundy(next_x, next_y, map)); } let mut g = 0; loop { if !grundy_nums.contains(&g) { map.insert((x, y), g); if g == 0 { println!("{} {}", x, y); } return g; } g += 1; } } struct Scanner { ptr: usize, length: usize, buf: Vec<u8>, small_cache: Vec<u8>, } impl Scanner { fn new() -> Scanner { Scanner { ptr: 0, length: 0, buf: vec![0; 1024], small_cache: vec![0; 1024], } } fn load(&mut self) { use std::io::Read; let mut s = std::io::stdin(); self.length = s.read(&mut self.buf).unwrap(); } fn byte(&mut self) -> u8 { if self.ptr >= self.length { self.ptr = 0; self.load(); if self.length == 0 { self.buf[0] = b'\n'; self.length = 1; } } self.ptr += 1; return self.buf[self.ptr - 1]; } fn is_space(b: u8) -> bool { b == b'\n' || b == b'\r' || b == b'\t' || b == b' ' } fn read<T>(&mut self) -> T where T: std::str::FromStr, T::Err: std::fmt::Debug, { let mut b = self.byte(); while Scanner::is_space(b) { b = self.byte(); } for pos in 0..self.small_cache.len() { self.small_cache[pos] = b; b = self.byte(); if Scanner::is_space(b) { return String::from_utf8_lossy(&self.small_cache[0..(pos + 1)]) .parse() .unwrap(); } } let mut v = self.small_cache.clone(); while !Scanner::is_space(b) { v.push(b); b = self.byte(); } return String::from_utf8_lossy(&v).parse().unwrap(); } }
CODE FESTIVAL 2016 Grand Final C - Cheating Nim
解法
Grundy 数の原理から、「各 もしくは の XOR の値を 0 にすることが出来るか?出来るなら を使った回数は何回か?」という問題に言い換えることができます。
ここで、ある i について を使うと XOR の値 x は になります。すなわち、現在の XOR の値と の XOR をとった値になります。ところで は必ず の形になります。証明もできますが、実験でもわかります。なので、貪欲に上のビットから潰していくことで解けます。
コード
fn main() { let mut sc = Scanner::new(); let n: usize = sc.read(); let a: Vec<u32> = (0..n).map(|_| sc.read()).collect(); let mut xor_sum = 0; for &a in &a { xor_sum ^= a; } let mut ans = 0; for bit in (0..30).rev() { if ((1 << bit) & xor_sum) == 0 { continue; } let x = (1 << (bit + 1)) - 1; for &a in &a { let y = a ^ (a - 1); if y == x { xor_sum ^= y; ans += 1; break; } } } if xor_sum != 0 { println!("-1"); } else { println!("{}", ans); } } struct Scanner { ptr: usize, length: usize, buf: Vec<u8>, small_cache: Vec<u8>, } impl Scanner { fn new() -> Scanner { Scanner { ptr: 0, length: 0, buf: vec![0; 1024], small_cache: vec![0; 1024] } } fn load(&mut self) { use std::io::Read; let mut s = std::io::stdin(); self.length = s.read(&mut self.buf).unwrap(); } fn byte(&mut self) -> u8 { if self.ptr >= self.length { self.ptr = 0; self.load(); if self.length == 0 { self.buf[0] = b'\n'; self.length = 1; } } self.ptr += 1; return self.buf[self.ptr - 1]; } fn is_space(b: u8) -> bool { b == b'\n' || b == b'\r' || b == b'\t' || b == b' ' } fn read<T>(&mut self) -> T where T: std::str::FromStr, T::Err: std::fmt::Debug, { let mut b = self.byte(); while Scanner::is_space(b) { b = self.byte(); } for pos in 0..self.small_cache.len() { self.small_cache[pos] = b; b = self.byte(); if Scanner::is_space(b) { return String::from_utf8_lossy(&self.small_cache[0..(pos + 1)]).parse().unwrap(); } } let mut v = self.small_cache.clone(); while !Scanner::is_space(b) { v.push(b); b = self.byte(); } return String::from_utf8_lossy(&v).parse().unwrap(); } }