1. 程式人生 > >【CF932G】Palindrome Partition 回文自動機

【CF932G】Palindrome Partition 回文自動機

names ast tdi 方式 ios 只需要 轉化 一半 暴力

【CF932G】Palindrome Partition

題意:給你一個字符串s,問你有多少種方式,可以將s分割成k個子串,設k個子串是$x_1x_2...x_k$,滿足$x_1=x_k,x_2=x_{k-1}...x_i=x{k-i+1}$。

$|s|\le 10^6$

題解:設字符串的長度為n,考慮字符串$T=s_1s_ns_2s_{n-1}...$。問題就轉化成了:求將原串劃分成若幹個長度為偶數的回文子串的方案數。

首先我們有一種暴力的想法,設f[i]表示將前i個字符分成若幹個回文子串的方案數,先跑回文自動機或Manacher得到所有$O(n^2)$個回文串。然後有轉移方程$f[i]=\sum f[j] [s(i,j)是回文串]$,特別地,對於奇數的i,f[i]=0。

但是觀察發現,如果$s(1,i)=A+DD..DT$,其中$T,DT,DDT...$都是回文串,那麽顯然有轉移$f[i]=f[i-|T|]+f[i-|DT|]+...$,而註意到對於$i-|T|$這個位置,也存在轉移$f[i-|T|]=f[i-|DT|]+f[i-|DDT|]+...$。也就是說,f[i]和f[i-|T|]有相當多的轉移是相同的!我們能不能利用這個性質來優化我們的算法呢?

接著,對於回文自動機上的每個節點,我們定義$diff[i]=len[i]-len[fail[i]]$,$sf[i]$表示i沿著fail指針往上走,走到的第一個$diff$與i不同的祖先。我們是不是可以把fail鏈上連續的diff相同的點放在一起呢?當然是可以的,有如下結論:

引理1:從一個點沿著sf指針向上走,最多走log步就能到達根節點。

證明很復雜,不過有一種比較容易的感性理解方法:就是當diff<len/2的時候,diff一定不變;diff>len/2的時候,diff又一定改變(畫畫圖能看出來)。所以每次len縮小一半,長度就是log的了。

為了保證正確性,我們還需要證明一個東西:

引理2:設j是i對應的節點在fail樹上的一個祖先,且diff[j]!=diff[fail[j]],則在所有以i-diff[j]結尾的回文串中,長度最長的為len[j]-diff[j]。

這個也不太會證,畫畫圖吧~

不過引理2保證了我們直接調用f[i-diff[j]]時不會統計到無關的信息。現在我們可以將diff相同的部分放到一起處理了,只需要維護一個類似於前綴和的東西,到時候暴力沿著sf向上走統計答案即可。具體可見代碼。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int P=1000000007;
const int maxn=1000010;
inline void upd(int &x,const int &y) {x+=y;	if(x>=P)	x-=P;}
int f[maxn],g[maxn];
int n,last,tot;
int ch[maxn][26],fa[maxn],len[maxn],diff[maxn],sf[maxn];
char ss[maxn],str[maxn];
inline void extend(int x)
{
	int p=last,c=str[x]-‘a‘;
	for(;str[x-len[p]-1]!=str[x];p=fa[p]);
	if(!ch[p][c])
	{
		int np=++tot,q=fa[p];	len[np]=len[p]+2;
		for(;str[x-len[q]-1]!=str[x];q=fa[q]);
		fa[np]=ch[q][c];
		ch[p][c]=np;
		diff[np]=len[np]-len[fa[np]];
		if(diff[np]==diff[fa[np]])	sf[np]=sf[fa[np]];
		else	sf[np]=fa[np];
	}
	last=ch[p][c];
}
int main()
{
	scanf("%s",ss+1),n=strlen(ss+1);
	int i,p;
	for(i=1;i+i<=n;i++)	str[i*2-1]=ss[i],str[i*2]=ss[n+1-i];
	tot=fa[0]=1,len[1]=-1;
	f[0]=1;
	for(i=1;i<=n;i++)
	{
		extend(i);
		p=last;
		for(p=last;p>1;p=sf[p])
		{
			g[p]=f[i-len[sf[p]]-diff[p]];
			if(diff[p]==diff[fa[p]])	upd(g[p],g[fa[p]]);
			upd(f[i],g[p]);
		}
		if(i&1)	f[i]=0;
	}
	printf("%d",f[n]);
	return 0;
}

【CF932G】Palindrome Partition 回文自動機