kmjp's blog

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

Codeforces ECR #110 : F. String Distance

ECRの最終問にしてはコードは短め。
https://codeforces.com/contest/1535/problem/F

問題

同じ長さの文字列a,bに対し、f(a,b)は以下を意味する。

  • aまたはbのどちらかの部分文字列を選び、その中を昇順にソートする。f(a,b)は、両者が一致するまでの操作回数。
  • ただし、どうやってもaとbが一致できないケースはf(a,b)=1337とする。

文字列集合Sが与えられる。Sの異なる要素対(a,b)におけるf(a,b)の総和を求めよ。

解法

sorted(a)!=sorted(b)な(a,b)におけるf(a,b)が1337確定である。
a!=bかつsorted(a)=sorted(b)の場合、少なくともa,b全体のソートをすればaとbが一致するので、f(a,b)が2以下なのは確定する。

そこで、まず各文字列をソートし、その時点で一致しない文字列対(a,b)はf(a,b)=1337として計上する。
一致する文字列対(a,b)はf(a,b)=2としていったん計上し、その後f(a,b)=1となるケースを差し引こう。
これは|a|の大きさで分岐させる。

  • |a|=|b|≦20の場合
    • Gを、入力の文字列のうちソートしたらaと同じになる文字列の集合とする。
    • aの部分列のソートを総当たりし、G内にそれが含まれていたら1引こう。
  • |a|=|b|>20の場合
    • 文字列対(a,b)を総当たりしよう。まず、両者共通するprefix・suffixを取り除く。
    • 残る文字列が、a,bいずれかが昇順であればf(a,b)=1で済むので1引こう。
int N,len;
string S[202020];
string T[202020];

map<string,vector<string>> M;

void solve() {
	int i,j,k,l,r,x,y; string s;
	
	cin>>N;
	ll ret=0;
	FOR(i,N) {
		cin>>S[i];
		len=S[i].size();
		T[i]=S[i];
		sort(ALL(T[i]));
		ret+=1337*i;
		ret-=1335*M[T[i]].size();
		M[T[i]].push_back(S[i]);
	}
	
	FORR2(a,V,M) {
		sort(ALL(V));
		
		if(len<=20) {
			set<string> G;
			FORR(v,V) G.insert(v);
			FORR(v,V) {
				FOR(y,len) FOR(x,y) {
					string w=v;
					sort(w.begin()+x,w.begin()+y+1);
					if(w[x]==v[x]) continue;
					if(w[y]==v[y]) continue;
					if(G.count(w)) ret--;
				}
			}
		}
		else {
			FOR(j,V.size()) FOR(i,j) {
				x=0;
				y=len-1;
				while(V[i][x]==V[j][x]) x++;
				while(V[i][y]==V[j][y]) y--;
				for(k=x;k<y;k++) if(V[i][k]>V[i][k+1]) break;
				if(k==y) ret--;
			}
		}
		
	}
	cout<<ret<<endl;
}

まとめ

重そうだと思ったけど、意外に時間かからないな。