google code jam 2011 Qualification Round C Candy Splitting
方針
これは、方針を考えるのに苦労した。けど、方針が分かればコーディングは一瞬。前の2問とは逆のパターン。
求められていることを、抽象的に述べると、次のとおりになる。
- 数列 a[i] (i = 1, ..., m) を空でない2つの数列 b[i] (i = 1, ..., n), c[i] (i = 1, ..., l)に分ける。
- 但し、b[1] ^ ... ^ b[n] = c[1] ^ ... ^ c[l] を満たさないと駄目。
- さらに、このような分割の中で、(b[1] + ... + b[n]) - (c[1] + ... + c[l])が最大になるものを探せ。
ここで、^は、ビット毎のXOR演算である。問題のサイズ的に、全探索は無理。
キモは、
x ^ y = 0 iff. x = y
が成り立つことである。これに気がつくと、
a[1] ^ ... ^ a[m] = 0
なら、任意の分割について、
(b[1] ^ ... ^ b[n]) ^ (c[1] ^ ... ^ c[l]) = 0
が成り立つことから
b[1] ^ ... ^ b[n] = c[1] ^ ... ^ c[l]
となることが分かる。従って、a[i]の最小値を分割の片方として、残りを他方とする分割が求めるものである。但し、
a[1] ^ ... ^ a[m] != 0
の場合には、条件を満たす分割は不可能である。
取り組み始めて、1時間くらい頭の中で考えていたけど、さっぱり分からなかった。で、数式に倒していじりはじめたら、割とすんなりと分かった。
回答
#include <iostream> #include <climits> #include <iomanip> using namespace std; void solve(int x){ int n; cin >> n; long sum = 0; long minval = 1000000; long buf; long check = 0; for(int i = 0; i < n; i++){ cin >> buf; sum += buf; minval = min(minval, buf); check ^= buf; } sum -= minval; if(check == 0){ cout << "Case #" << x << ": " << sum << endl; } else{ cout << "Case #" << x << ": " << "NO" << endl; } } int main(int argc, char *argv[]){ int n; cin >> n; for(int i = 0; i < n; ++i){ solve(i + 1); } return 0; }