mayoko’s diary

プロコンとかいろいろ。

GCJ Round 1B Problem C. Technobabble

この問題とは関係ないですが windows でそれっぽくプログラミングができるようになりました。

問題

Dashboard - Round 1B 2016 - Google Code Jam

前半, 後半に分かれたワードの組が N 個与えられる。
あるワードの組 (s, t) について, 「すでに s と同じ文字列が前半ワードとして登場している」かつ「すでに t と同じ文字列が後半ワードとして登場している」が成り立つとき, カウントを 1 足すことにする

N 個のワードの組をいい感じに並び替えることによって, カウントの値を最大化せよ。

解法

細かい順番は関係なしに,
「登場済みフラグを立てるための文字の組」と「カウントを稼ぐ文字の組」の 2 グループに分ければ良いです。

一回すべてのワードが登場済みになれば, あとは文字を入れるごとにカウントが 1 増えていくので, 「登場済みフラグを立てるための文字の組」を最小化すれば良いことに気付きます。あとはこれが解ければ勝ちです。

ところで, ワードの組 (former[i], after[i]) について 二部グラフを作ることを考えると, 上記の問題は最小辺カバーであることがわかります。(最小辺カバー) + (最大マッチング)= (マッチングに登場する頂点の数) が成り立つので, 結局最大流の問題になり, 解くことができます。

void solve() {
	int N;
	cin >> N;
	vector<string> former(N), after(N);
	for (int i = 0; i < N; i++) {
		cin >> former[i] >> after[i];
	}
	map<string, int> fp, ap;
	for (int i = 0; i < N; i++) {
		fp[former[i]] = 0;
		ap[after[i]] = 0;
	}
	{
		int k = 0;
		for (auto& p : fp) 
			p.second = k++;
	}
	{
		int k = 0;
		for (auto& p : ap) 
			p.second = k++;
	}
	int s = fp.size() + ap.size();
	int t = s+1, V = t+1;
	Dinic dinic(V);
	for (int i = 0; i < N; i++) {
		int v = fp[former[i]], u = fp.size() + ap[after[i]];
		dinic.add_edge(v, u, 1);
	}
	for (int i = 0; i < fp.size(); i++) dinic.add_edge(s, i, 1);
	for (int i = 0; i < ap.size(); i++) dinic.add_edge(i+fp.size(), t, 1);
	cout << N-(s-dinic.max_flow(s, t)) << endl;
}

int main() {
	cin.tie(0);
	ios::sync_with_stdio(false);
	int T;
	cin >> T;
	for (int t = 1; t <= T; t++) {
		cout << "Case #" << t << ": " ;
		solve();
	}
	return 0;
}