kmjp's blog

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

VK Cup 2015 Round 1 : A. And Yet Another Bracket Sequence

SuffixArrayデビューしました。
http://codeforces.com/contest/529/problem/A

問題

開き括弧と閉じ括弧で構成されるN文字の文字列Sが構成される。
この文字に、以下の処理を任意回数行うことができる。

  • Sを回転させる。
  • Sの任意の位置に括弧を追加する。

その結果得られるSのうち、括弧の対応が取れている文字列(correct bracket sequence)の中で最短のものを答えよ。
最短のものが複数あるなら、辞書順最小のものを答えよ。

解法

まずS中の開き括弧と閉じ括弧の文字数を数える。
辞書順最小の条件より、括弧の追加位置は以下の通りになる。

  • 閉じ括弧の方が多いなら、Sを回転したのち足りない分先頭に開き括弧をつける。
  • 開き括弧の方が多いなら、Sを回転したのち足りない分末尾に閉じ括弧をつける。

言い換えると、(回転後の)Sの途中に括弧を追加することはない。
あとはSを回転して以下の条件を満たすものを見つけたい。

  • 辞書順最小であること。
  • 文字列がcorrectであること。すなわちSのうち閉じ括弧の方が多くなるprefixがない。

前者については、Sを2回繰り返したものに対しSuffixArrayを求めることで比較できる。

後者について、P(x)をS(を2連結したもの)のx文字のprefixにおける(開きかっこ数)-(閉じかっこ数)とする。
Sのx文字目を先頭になるよう回転させたとき、correctであるかどうかはP(x)=min(P(x)~P(x+N))であればよい。
すなわちx文字目から見てN文字の間に置いて、閉じ括弧の方が多くなる場合がないことになる。
ただし、全体で閉じ括弧の方が多い場合、先頭に足りない分開きかっこを追加できるのでその分を考慮すること。
min(P(x)~P(x+N))はSegTreeで管理しても良いし、尺取法の要領で行うこともできる。

この問題はNが最大10^6と大きいため、O(NlogN)かかるSAだと結構時間がギリギリ。
SA-ISだと余裕があるようなのだが…ちょっとN大きすぎないかね。

struct SuffixArray {
	int N; vector<int> rank,lcp,sa; string S;
	
	SuffixArray(string S) : S(S){
		int i,h=0; vector<int> tmp,tr;
		N=S.size(); rank.resize(N+1); sa.resize(N+1); tmp.resize(N+1);
		FOR(i,N+1) sa[i]=i, rank[i]=i==N?-1:S[i];
		
		for(int k=1; k<=N; k<<=1) {
			auto pred2 = [k,this](int& a,int &b)->bool{ return (((a+k<=N)?rank[a+k]:-1)<((b+k<=N)?rank[b+k]:-1));};
			auto pred = [pred2,k,this](int& a,int &b)->bool{ return (rank[a]!=rank[b])?(rank[a]<rank[b]):pred2(a,b);};
			int x=0;
			if(k!=1) for(i=1;i<N+1;i++) if(rank[sa[i]]!=rank[sa[x]]) sort(sa.begin()+x,sa.begin()+i,pred2), x=i;
			sort(sa.begin()+x,sa.end(),pred);
			FOR(i,N+1) tmp[sa[i]]=(i==0)?0:tmp[sa[i-1]]+pred(sa[i-1],sa[i]);
			swap(rank,tmp);
		}
		lcp.resize(N+1); tr.resize(N+1);
		FOR(i,N+1) tr[sa[i]]=i;
		FOR(i,N) {
			int j=sa[tr[i]-1];
			for(h=max(h-1,0);i+h<N && j+h<N; h++) if(S[j+h]!=S[i+h]) break;
			lcp[tr[i]-1]=h;
		}
	}
};

string S;
int L,P;
int score[2500000];
int sd[4500000];
const int OFF=2200000;
int mi=OFF;

void solve() {
	int i,j,k,l,r,x,y; string s;
	
	cin>>S;
	L=S.size();
	S+=S;
	SuffixArray sa(S);
	
	FOR(i,L) {
		score[i+1]=score[i] + ((S[i]=='(')?1:-1);
		sd[OFF+score[i+1]]++;
		mi=min(mi,OFF+score[i+1]);
	}
	int dif=score[L];
	int ret=-1;
	FOR(i,L) {
		if((mi-OFF)-score[i] >= min(0,dif) && (ret == -1 || sa.rank[i]<=sa.rank[ret])) ret = i;
		
		score[i+L+1]=score[i+L] + ((S[i+L]=='(')?1:-1);
		sd[OFF+score[i+L+1]]++;
		mi=min(mi,OFF+score[i+L+1]);
		if(--sd[OFF+score[i+1]]==0 && mi==OFF+score[i+1]) mi += (sd[OFF+score[i+1]-1])?-1:1;
	}
	
	if(dif<0) cout<<string(-dif,'(');
	cout<<S.substr(ret,L);
	if(dif>0) cout<<string(dif,')');
	cout<<endl;
}

まとめ

最近CFでFFTとSAデビューできてよかった。