【CF506E】Mr. Kitayuta's Gift dp轉有限狀態自動機+矩陣乘法

【CF506E】Mr. Kitayuta's Gift

題意:給你一個字符串s,你需要在s中插入n個字符(小寫字母),每個字符可以被插在任意位置。問可以得到多少種本質不同的字符串,使得這個串是迴文的。答案對10007取模。

$|s|\le 200,n\le 10^9$

題解:神題。

首先由於題目要求本質不同,所以我們爲了防止重複,考慮從兩邊向中間不斷復原迴文串,如果新加入的字符與s兩端(或一端)的字符相同,則匹配成功,繼續匹配下一個字符。也就是說我們取的是s在迴文串中最外面的出現位置。

爲了方便,我們先只考慮n+|s|爲偶數的情況,可以設出DP狀態:設f[i][a][b]表示從外往裏加入了i個字符,原串左邊匹配到了a,有邊匹配到了b的方案數。當新加入一個字符時,我們根據它是否與a和b匹配來確定轉移到哪個狀態。我們發現這個過程其實是在一個有限狀態自動機上匹配的過程。對於s=abaac,轉移的過程如下圖:

 

其中GOAL代表匹配完畢,它之後可以接任何字符,所以有26條自環。對於紅點,它代表的狀態兩端的字符不相同,所以他有2條出邊,24條自環。對於綠點,它代表的狀態兩端的字符相同,所以有1條出邊和25條自環。特別地,能轉移到GOAL的點都是綠點。

直接建出來這個自動機顯然節點數目是$|s|^2$的,無法用矩乘優化。所以我們考慮對這個自動機進行壓縮。可以發現,整個自動機其實可以被拆成若干條鏈,其中一條鏈上如果有i個紅點,就有$\lceil {|s|-i\over 2}\rceil$個綠點。既然我們已經把自動機拆成鏈了,那麼每條鏈上紅點綠點的順序也就無關緊要了,我們只需要知道每條鏈上紅點與綠點的數目。換句話說,我們需要知道有多少條鏈有i個紅點,這樣一來本質不同的鏈就只有|s|條了。

統計方法比較簡單,用g[a][b][i]表示在所有從起始節點走到(i,j)這個節點的路徑中,有多少條已經走了i個紅點。轉移複雜度$|s|^3$。

既然我們已經有了|s|種鏈的各自數目,我們就可以想辦法用$O(|s|)$個節點來構建一個新的自動機了。到這裏我的方法和官方做法出現了分歧,個人認爲我的做法比較簡單。

構建|s|個紅點串成一串,從起點指出來;$\lceil{|s|\over 2}\rceil$個綠點串成一串,指向終點。對於一種串$(i,\lceil {|s|-i\over 2}\rceil)$,我們從第i個紅點向第$\lceil {|s|-i\over 2}\rceil$個綠點連一條邊即可。這樣一來點數就是${3\over 2}|s|$,可以用矩乘優化。如下圖:

 

那麼對於n+|s|爲奇數的情況呢?我們先進行$n+|s|+1\over 2$步矩乘,但是在最後一步時形如(i,i+1)的綠點是不能直接轉移到終點的。於是我們要將這些轉移的貢獻減去,方法是將(i,i+1)這樣的點設爲終點(無自環),重新建圖跑一邊矩乘即可。

本題的矩乘優化常數小技巧:發現我們只能從編號小的點轉移到編號大的點,所以j只需要從i枚舉到n,k只需要從i枚舉到j即可。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int P=10007;
int n,m,N;
int f[210][210][210],g[210];
struct M
{
	int v[310][310];
	int * operator [] (const int &a) {return v[a];}
	M () {memset(v,0,sizeof(v));}
	inline M operator * (const M &a) const
	{
		M b;
		int i,j,k;
		for(i=0;i<=N;i++)	for(j=i;j<=N;j++)	for(k=i;k<=j;k++)	b.v[i][j]=(b.v[i][j]+v[i][k]*a.v[k][j])%P;
		return b;
	}

}S,T;
char str[210];
inline void pm(int y)
{
	while(y)
	{
		if(y&1)	S=S*T;
		T=T*T,y>>=1;
	}
}
int main()
{
	scanf("%s%d",str,&m),n=strlen(str);
	int i,j,k;
	f[0][n-1][0]=1;
	for(i=0;i<n;i++)
	{
		for(j=n-1;j>=i;j--)
		{
			if(str[i]==str[j])
			{
				for(k=0;k<i+n-j;k++)
				{
					if(i+1<j)	f[i+1][j-1][k]=(f[i+1][j-1][k]+f[i][j][k])%P;
					else	g[k]=(g[k]+f[i][j][k])%P;
				}
			}
			else
			{
				for(k=0;k<i+n-j;k++)
				{
					f[i+1][j][k+1]=(f[i+1][j][k+1]+f[i][j][k])%P;
					f[i][j-1][k+1]=(f[i][j-1][k+1]+f[i][j][k])%P;
				}
			}
		}
	}
	N=n+(n+1)/2+1;
	S[0][1]=1,S[0][N-(n+1)/2]=g[0],T[N][N]=26;
	for(i=1;i<=n;i++)
	{
		T[i][i]=24,T[i][N-(n-i+1)/2]=g[i];
		if(i!=n)	T[i][i+1]=1;
	}
	for(i=n+1;i<N;i++)	T[i][i+1]=1,T[i][i]=25;
	if((n+m)&1)
	{
		pm((n+m+1)>>1);
		int ans=S[0][N];
		memset(S.v,0,sizeof(S.v));
		memset(T.v,0,sizeof(T.v));
		memset(g,0,sizeof(g));
		for(i=0;i<n-1;i++)	if(str[i]==str[i+1])	for(k=0;k<=n;k++)
		{
			g[k]=(g[k]+f[i][i+1][k])%P;
		}
		S[0][1]=1,S[0][N-(n+1)/2]=g[0];
		for(i=1;i<=n;i++)
		{
			T[i][i]=24,T[i][N-(n-i+1)/2]=g[i];
			if(i!=n)	T[i][i+1]=1;
		}
		for(i=n+1;i<N;i++)	T[i][i+1]=1,T[i][i]=25;
		pm((n+m+1)>>1);
		printf("%d",(ans-S[0][N]+P)%P);
	}
	else
	{
		pm((n+m)>>1);
		printf("%d",S[0][N]);
	}
	return 0;
}//abaac 2
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章