kmjp's blog

競技プログラミング参加記です

AtCoder ARC #105 : E - Keep Graph Disconnected

うーん、こういうゲーム系苦手。
https://atcoder.jp/contests/arc105/tasks/arc105_e

問題

N頂点M辺の無向グラフが与えられる。
初期状態で頂点1とNは非連結である。
ここで2人で交互に辺を1本追加するゲームを行う。

辺を追加する際、多重辺やループを作ってはならない。
また、頂点1とNが連結になってはならない。
両者最適手を取るとき、勝者はどちらか。

解法

自分の手番で、完全グラフ×2の形にすると次に相手が詰む。
Nが奇数の時、完全グラフ×2となったときに存在する辺の数の偶奇は固定なので、詰む手番は辺の数の偶奇だけ見れば確定できる。
Nが偶数の時、完全グラフ×2となったときに両者の頂点数が偶数・偶数か奇数・奇数かで辺の数の偶奇が変化するので、勝者はそこまでの手順できまる。

初期状態で頂点1とNの連結成分の頂点数の偶奇が分かれているとき、先手必勝である。
初手で先手が偶数・偶数か奇数・奇数のうち勝てるほうに遷移させておくと、後手が偶数・奇数のパターンにしても戻せるし、後手が偶数・偶数か奇数・奇数をキープしようとしたとき、先手も再度それをキープできる辺が余っている。

問題は初期状態で先手が偶数・偶数か奇数・奇数のうち勝てない方になっている場合、毎ターン先手の行動は反対に後手に押し戻されるので後手必勝。

int T;
int N,M;
int win[101010];

class UF {
	public:
	vector<int> par,rank,cnt;
	void reinit(int num) {par=rank=vector<int>(num,0); cnt=vector<int>(num,1); for(int i=0;i<num;i++) par[i]=i;}
	int operator[](int x) {return (par[x]==x)?(x):(par[x] = operator[](par[x]));}
	int count(int x) { return cnt[operator[](x)];}
	int operator()(int x,int y) {
		if((x=operator[](x))==(y=operator[](y))) return x;
		cnt[y]=cnt[x]=cnt[x]+cnt[y];
		if(rank[x]>rank[y]) return par[x]=y;
		rank[x]+=rank[x]==rank[y]; return par[y]=x;
	}
};
UF uf;

void solve() {
	int i,j,k,l,r,x,y; string s;
	
	cin>>T;
	while(T--) {
		cin>>N>>M;
		uf.reinit(N);
		FOR(i,M) {
			cin>>x>>y;
			x--,y--;
			uf(x,y);
		}
		vector<int> V;
		V.push_back(uf.count(0));
		V.push_back(uf.count(N-1));
		for(i=1;i<N-1;i++) if(uf[i]==i&&uf[i]!=uf[0]&&uf[i]!=uf[N-1]) V.push_back(uf.count(i));
		
		int C[2]={};
		for(i=uf.count(0);i<=N-uf.count(N-1);i++) {
			ll tot=1LL*i*(i-1)/2+1LL*(N-i)*(N-i-1)/2;
			ll add=tot-M;
			win[i]=(add%2)^1;
			C[win[i]]++;
		}
		
		if(C[0]==0) {
			cout<<"Second"<<endl;
		}
		else if(C[1]==0) {
			cout<<"First"<<endl;
		}
		else {
			if(V[0]%2!=V[1]%2) {
				cout<<"First"<<endl;
			}
			else {
				ll a=1LL*N*(N-1)/2-1LL*V[0]*V[1]-M;
				if(a%2==0) {
					cout<<"Second"<<endl;
				}
				else {
					cout<<"First"<<endl;
				}
			}
		}
	}
}

まとめ

こういうの自信をもって書けないなぁ。