kmjp's blog

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

Codeforces ECR #053 : G. Yet Another LCP Problem

こちらも実装面倒だけどまぁECRだしね…。
http://codeforces.com/contest/1073/problem/G

問題

文字列Sが与えられる。
以下のクエリQ個に順に答えよ。
各クエリは、Sの添え字の集合A,Bからなる。
各A[i],B[j]の対に対し、S[A[i]...]とS[B[i]...]の最長共通接頭辞の総和を求めよ。

解法

f(A,B) := 問題文に示す2つの集合A,Bから得られる値
とする。2つの集合を考えると面倒なので、以下を考えよう。
g(X) := u,v∈Xの対に対しS[u...]とS[v...]の最長共通接頭辞の総和

こうするとf(A,B) = g(A∪B) - g(A) - g(B)となるので、結局g(X)を実装すればよいことになる。
最長接頭辞と言えばまずはSuffixArrayを求めよう。
X内の添え字をSuffixArray中の順番で小さい順にソートし、各添え字について、その手前に登場する添え字とのLCPの総和を求めていけばよい。
これはRMQが解けるSegTreeと、LCP長と対応する添え字数の対応関係を保持するmapを1個もてば求められる。

int N,Q;
string S;

struct SuffixArray {
	int N; vector<int> rank,lcp,sa,rsa; string S;
	
	SuffixArray(string S) : S(S){
		int i,h=0; vector<int> tmp;
		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); rsa.resize(N+1);
		FOR(i,N+1) rsa[sa[i]]=i;
		FOR(i,N) {
			int j=sa[rsa[i]-1];
			for(h=max(h-1,0);i+h<N && j+h<N; h++) if(S[j+h]!=S[i+h]) break;
			lcp[rsa[i]-1]=h;
		}
	}
};
template<class V,int NV> class SegTree_1 {
public:
	vector<V> val;
	static V const def=1<<20;
	V comp(V l,V r){ return min(l,r);};
	
	SegTree_1(){val=vector<V>(NV*2,0);};
	V getval(int x,int y,int l=0,int r=NV,int k=1) { // x<=i<y
		if(r<=x || y<=l) return def;
		if(x<=l && r<=y) return val[k];
		return comp(getval(x,y,l,(l+r)/2,k*2),getval(x,y,(l+r)/2,r,k*2+1));
	}
	void update(int entry, V v) {
		entry += NV;
		val[entry]=v;
		while(entry>1) entry>>=1, val[entry]=comp(val[entry*2],val[entry*2+1]);
	}
};

SuffixArray SA("");
SegTree_1<int,1<<20> st;

ll hoge(vector<pair<int,int>> V) {
	sort(ALL(V));
	map<int,int> M;
	ll ret=0;
	int pre=-1;
	ll S=0;
	
	FORR(v,V) {
		int l=N-v.second;
		int m=l;
		if(pre>=0 && pre<v.first) m=st.getval(pre,v.first);
		pre=v.first;
		
		
		while(M.size() && M.rbegin()->first>m) {
			S-=(M.rbegin()->first-m)*M.rbegin()->second;
			M[m]+=M.rbegin()->second;
			M.erase(M.rbegin()->first);
		}
		ret+=S;
		M[l]++;
		S+=l;
	}
	
	return ret;
}

void solve() {
	int i,j,k,l,r,x,y; string s;
	
	cin>>N>>Q>>S;
	SA=SuffixArray(S);
	
	FOR(i,N+1) st.update(i,SA.lcp[i]);
	while(Q--) {
		vector<pair<int,int>> A,B,C;
		cin>>x>>y;
		while(x--) {
			cin>>i;
			A.push_back({SA.rsa[i-1],i-1});
			C.push_back({SA.rsa[i-1],i-1});
		}
		while(y--) {
			cin>>i;
			B.push_back({SA.rsa[i-1],i-1});
			C.push_back({SA.rsa[i-1],i-1});
		}
		
		cout<<(hoge(C)-hoge(A)-hoge(B))<<endl;
	}
}

まとめ

これは実装が面倒だしECRじゃない通常回で出すと嫌がられそう。