kmjp's blog

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

yukicoder : No.263 Common Palindromes Extra

そこまで実装は難しくないかも。
http://yukicoder.me/problems/399

問題

アルファベット大文字で構成された2つの文字列S,Tが与えられる。
以下の条件を満たす(i,j,k,l)の組み合わせ数を求めよ。

  • i,j,k,lはS[i..j]、T[k..l]が有効な部分文字列を指すような範囲の数値である。
  • S[i..j]=T[k..l]
  • S[i..j]は回文である。

解法

文字列Vの回文となる部分文字列を列挙する方法は、Manacher法をベースにしたりDPをしたりする方法があるがどちらもO(|V|^2)かかるので今回のケースでは利用できない。
そこでEditorial通りPalindromicTreeを使う。
PalindromicTree自体の説明はEditorialにあるリンク先を参照。

V=S+"<>"+TのようにSとTの間にアルファベットでない2文字を挟んだ文字列を考える。
ここでVを1文字ずつPalindromicTreeの生成処理にかけ、各文字の処理後のノードを求めておく。
このノードは、各文字位置を末尾とする最長回文に相当する。
またPalindromicTreeの構成上、各ノードはそのノードに対応する回文のうち、最長のsuffix palindromeへのリンクを保持していくので、そこからDPの要領でより短い回文の登場回数を数え上げることができる。

Vの処理をする際、Sに対応する部分の数え上げた結果と、Tに対応する部分の数え上げた結果それぞれに対して短い回文の登場回数を数え上げれば、あとは各ノード毎に両者の積を取ればよい。

Editorialで紹介しているサイトのコードは、ノード間のリンクを文字列毎の配列で取っている。
今回のケースだと文字種をσ=28種とするので、4byte整数を|V|*σ個で112MB程度取る羽目になり、他の数え上げパートなどのデータも含めるとMLEする。
よってノード間のリンクを3byte値にpackingしたり、mapで持つことでMLEを回避できる。

なぜ今回Palindrome Treeなら解けるかを少し考えてみた。
最初の考察通り、文字列Vに対し回文となる部分文字列の位置はO(|V|^2)通りあるのでこれを数え上げるとTLEする。
しかし、位置が異なっても同じ回文は同一視すると、そもそも登場する回文となる部分文字列はO(|V|)個しか現れない。
(これはPalindromicTreeの生成アルゴリズムからも明らか。1文字処理毎に1ノードしか増えない)

すなわち、PalindromicTreeを用いた数え上げでは、「位置が異なるが同じ文字列」に対する処理をまとめこめるので高速化できると考えることができる。

struct PalindromicTree {
	struct node {
		map<char,int> next;
		int len, sufflink,num,first;
	};
	string S;
	vector<node> V;
	int n,s; // num,suff
	
	bool add(int pos) {
		char ch=S[pos];
		int c=s, cl=0;
		while(1) {
			cl=V[c].len;
			if(pos-1-cl>=0 && S[pos-1-cl]==ch) break;
			c=V[c].sufflink;
		}
		
		if(V[c].next.count(ch)) {
			s = V[c].next[ch];
			return false;
		}
		
		s=++n;
		V[n].len=V[c].len+2;
		V[n].first=pos;
		V[c].next[ch]=n;
		
		if(V[n].len==1) { // even length
			V[n].sufflink=2;
			V[n].num=1;
			return true;
		}
		
		while(1) {
			c=V[c].sufflink;
			cl=V[c].len;
			if(pos-1-cl>=0 && S[pos-1-cl]==ch) {
				V[n].sufflink=V[c].next[ch];
				break;
			}
		}
		V[n].num=1+V[V[n].sufflink].num;
		return true;
	}
	
	void init(string S) {
		this->S=S;
		V.clear();
		V.resize(S.size()+10);
		
		n=s=2;
		V[1].first=V[2].first=-1;
		V[1].len=-1;
		V[2].len=0;
		V[1].sufflink=V[2].sufflink=1;
	}
};


string S,T,V;
vector<ll> dp[2];

void solve() {
	int i,j,k,l,r,x,y; string s;
	
	cin>>S>>T;
	V=S+"[\\"+T;
	dp[0].resize(V.size()+5);
	dp[1].resize(V.size()+5);
	
	
	PalindromicTree pt;
	pt.init(V);
	
	
	FOR(i,V.size()) {
		pt.add(i);
		if(i<S.size()) dp[0][pt.s]++;
		if(i>=S.size()+1) dp[1][pt.s]++;
	}
	ll ret=0;
	for(i=pt.n;i>=3;i--) {
		ret += dp[0][i]*dp[1][i];
		dp[0][pt.V[i].sufflink]+=dp[0][i];
		dp[1][pt.V[i].sufflink]+=dp[1][i];
	}
	cout<<ret<<endl;
}

まとめ

PalindromicTreeの実装、まだちょっと元サイトの実装を理解しきれてないのでもう少し考える。