【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