kmjp's blog

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

Codeforces #260 Div1 B. A Lot of Games

Trieを思いついたのは良かったけど、ちょっとWAした。
http://codeforces.com/contest/455/problem/B

問題

文字列集合が与えられるので、2人でゲームを行う。
このゲームでは空文字列から初めて2人で交互に文字列の末尾に1文字追加する。
この時得られる文字列は、文字列集合中のいずれかの文字列のprefixでなければならない。

条件を満たすような末尾1文字がなければ、そのプレイヤーは負けである。
また、条件を満たすような末尾1文字が存在するなら、そちらに遷移しなければならない(あきらめて負けにすることはできない)

このようなゲームをK回行う。
i回目の敗者が(i+1)回目の勝者となる。
両者最適手を取ると、最後K回目の勝者は先手後手どちらか。

解法

文字列集合からTrieを作り、その上でDFSすることで、先手が以下のいずれの戦略を取れるか判定する。

  • 先手は勝ち負けを選択できる
    • この場合、先手は(K-1)回目までわざと負けて毎回先手をキープしつつK回目だけ勝てばよいので先手必勝。
  • 先手は必ず勝つ
    • 毎回勝者が入れ替わるので、Kの偶奇で最後の勝者が決まる。
  • 先手は必ず負ける
    • 毎回負ける上、毎回先手を取らせられるので先手がK回負け続けるので、後手必勝。
ll N,K;
vector<string> S;

class Trie {
public:
	vector<vector<int> > V;
	int find(string s) {
		int cur=0;
		ITR(it,s) if((cur=V[cur][*it])==0) return -1;
		return cur;
	}
	void create(vector<string> S) {
		V.clear();
		V.push_back(vector<int>(256));
		sort(S.begin(),S.end());
		ITR(it,S) {
			int cur=0;
			ITR(c,(*it)) {
				if(V[cur][*c]==0) V.push_back(vector<int>(256)),V[cur][*c]=V.size()-1;
				cur=V[cur][*c];
			}
		}
	}
};

Trie t;

bool winwin(int cur) {
	int i;
	FOR(i,256) if(t.V[cur][i]!=0) {
		if(winwin(t.V[cur][i])==false) return true;
	}
	return false;
}

bool loselose(int cur) {
	int i,num=0;
	
	FOR(i,256) if(t.V[cur][i]!=0) {
		if(loselose(t.V[cur][i])==false) return true;
		if(loselose(t.V[cur][i])==true) num++;
	}
	if(num) return false;
	return true;
}

void solve() {
	int f,i,j,k,l,x,y;
	cin>>N>>K;
	FOR(i,N) {
		string s;
		cin>>s;
		S.push_back(s);
	}
	t.create(S);
	bool b1=winwin(0);
	bool b2=loselose(0);
	if(!b1) {
		cout << "Second" << endl;
	}
	else {
		if(b2) {
			cout << "First" << endl;
		}
		else {
			if(K%2) cout << "First" << endl;
			else cout << "Second" << endl;
		}
	}
	
}

まとめ

Trieをライブラリ化しておいてよかった。
先手が狙って負けられるかのDFSの書き方に苦戦して3WAした。