kmjp's blog

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

Codeforces #445 Div1 B. Restoration of string

Pretestが甘いこともあり、しょうもないミスをしたままSubmitしてしまった。
http://codeforces.com/contest/889/problem/B

問題

N個の文字列T[i]が与えられる。

文字列Sに対し、別の文字列が良い文字列であるとは、その文字列がS中に部分列として登場する回数が最多(同着の他の文字列があってもよい)ことである。
入力の文字列群に対し、それらが良い文字列となるような文字列Sとしてあり得るもののうち、辞書順最小のものを求めよ。

解法

同じアルファベットが2回出てしまうと、良い文字列は2回以上部分列として登場しなければならないため、同じアルファベットが2回登場することはない。
ここで各文字列T[i]について考える。
T[i][j]とT[i][j+1]は元の文字列S中で連続して登場しなければならない、という関係を覚えておこう。

アルファベットの各文字を頂点としたグラフにおいて、この関係を有向辺として張ろう。
すると、このグラフは閉路を持たず、各入次数・出次数は1以下でなければならない。

逆に各入次数・出次数が1以下なら、このグラフはいくつかの有向パスによって、各頂点を1回ずつ通り、かつ辺も1回ずつ通るよう分解できる。
元のT中に登場するアルファベットのうち、パスの始点となるもののアルファベット昇順で、パスの要素を並べていけばよい。

int N;
int cand[26];
set<int> from[26],to[26];
string S;

void solve() {
	int i,j,k,l,r,x,y; string s;
	
	cin>>N;
	FOR(i,N) {
		cin>>S;
		FORR(c,S) {
			c-='a';
			cand[c]=1;
		}
		FOR(j,S.size()-1) {
			from[S[j+1]].insert(S[j]);
			to[S[j]].insert(S[j+1]);
		}
	}
	
	FOR(i,26) {
		if(from[i].size()>1 || to[i].size()>1) return _P("NO\n");
	}
	
	string R;
	FOR(i,26) if(cand[i]==1 && from[i].size()==0) {
		int cur=i;
		while(1) {
			R+=(char)('a'+cur);
			if(to[cur].empty()) break;
			cur=*to[cur].begin();
		}
	}
	
	if(R.size()!=count(cand,cand+26,1)) R="NO";
	cout<<R<<endl;
	
}

まとめ

なぜしょうもないミスをしたのか…。