AGC033: C - Removing Coins
問題概要
頂点からなる木が与えられる。 番目の辺は と を結んでいる。
最初それぞれの頂点にコインが枚おいてある。高橋くんと青木くんはそれぞれコインが置いてある頂点 をつ選びその頂点に置いてあるコインをすべて取り,そのあとすべてのコインを に距離 だけ近づけるという操作を繰り返す。
操作ができなくなった人を負けとすると,お互いに最善を尽くしたときどちらが勝つかを求めよ。
(問題文は https://atcoder.jp/contests/agc033/tasks/agc033_c に記載されている。)
制約
- 与えられるグラフは木
アイディア
回の操作でコインは選んだ頂点に少しずつ引き寄せられるので,葉となる頂点からはコインは出ていくが入ってこない。つまり,回の操作はグラフから葉となる頂点をすべて消す(ただし選んだ頂点については葉であっても当然消されない)のと同値である。
ここで,木上の最長パスのうちのつ(このパスをとする)についてのみ考えることとする。この最長パスの端点をとするとかを選ぶとこのパスの長さは減り,以外の頂点を選んだ場合についてはこのパスの長さは減ることになる。
次に,上にない頂点について考える。葉ではない頂点を選ぶとき,すべての葉が消されるので,よりも長いパスができることはなく,のパスを減らすこととなる。また,のどちらかを選ぶときも同様にのうち選ばれなかった頂点とすべての葉が消されるので,よりも長いパスができることはなく,のパスを減らすこととなる。
さらに,上にない葉を消すことを考えると,もしもその葉がとは別の最長パスの端点であったとしたらそのパスを新たにとして考えることができ(つまりの長さがだけ減る),それ以外のときはより長いパスができることはないので結局のパスを減らすこととなる。
以上のことから,1回の操作はの長さをまたは減らすことと同値であることが分かる。
実装
木の最長パスを一般に直径という。直径の長さ(及びその長さを持つパス)はどの頂点でもよいので頂点を1つ選びとし,から最も遠い頂点のつをとして,からもっとも遠い頂点のつをとすると,とを端点とするパスの長さと一致する。とは簡単なBFSで求められるので,直径の長さはで求まる。
そして,直径の長さをもとに青木くんが勝つか高橋くんが勝つかは単純なメモ化再帰*1で求めることができる。この計算量はである。
よって全体的な計算量はである。
コード
#include <iostream> #include <vector> using namespace std; const int INF = (1<<30); vector<int> g[225816],q[2]; int dist[225816]; bool done[225816],memo[225816]; bool solve(int r){ if(done[r]){ return memo[r]; } done[r] = true; if(r==0){ memo[r] = true; }else if(r==1){ memo[r] = false; }else{ memo[r] = (!(solve(r-1)&&solve(r-2))); } return memo[r]; } int main(void){ int N,a,b,i,j,k; cin >> N; for(i=1; i<N; ++i){ cin >> a >> b; --a; --b; g[a].push_back(b); g[b].push_back(a); } a = 0; for(k=0; k<2; ++k){ fill(dist,dist+N,INF); q[k].push_back(a); dist[a] = 0; for(i=0; i<q[k].size(); ++i){ a = q[k][i]; for(j=0; j<g[a].size(); ++j){ b = g[a][j]; if(dist[b] > dist[a]+1){ dist[b] = dist[a]+1; q[k].push_back(b); } } } } cout << ((solve(dist[a]))?"First":"Second") << endl; return 0; }
感想
コンテスト中にを通したので喜んでいたら任意人類が通していて「うーん」となってしまった。問題自体はとても綺麗で好き。