P4590-[TJOI2018]游园会【dp套dp】
正題
題目鏈接:https://www.luogu.com.cn/problem/P4590
題目大意
給出一個長度為mmm的字符串sss。
對于每個k∈[0,m]k\in[0,m]k∈[0,m]求有多少個長度為nnn的字符串滿足與sss的最長公共子序列長度為kkk且不包含NOINOINOI這一個子串。
可用字符集是{N,O,I}\{N,O,I\}{N,O,I}
解題思路
顯然這個NOINOINOI的限制是很無聊的,先不管。
然后就是求最長公共子序列恰好為kkk,之前翻資料的時候看到過這題,然后mmm又只有151515,所以可以直接dpdpdp套dpdpdp。
先考慮正常dpdpdp求最長公共子序列,就是設gi,jg_{i,j}gi,j?表示第一個串匹配到iii,第二個串匹配到jjj時的長度。那么顯然對于一個iii來說是可以對應多個jjj的。
然后我們要在轉移dpdpdp的自動機上對于iii維護每個gi,jg_{i,j}gi,j??
雖然mmm很小但是這個狀態還是很多,要加點優化。挖掘一下ggg數組的性質發現其實有gi,j?1≤gi,j≤gi,j?1+1g_{i,j-1}\leq g_{i,j}\leq g_{i,j-1}+1gi,j?1?≤gi,j?≤gi,j?1?+1。所以可以狀壓一下,用111表示這里加了111,000表示沒有加一就可以表示出所有的狀態了。
然后先預處理出每個狀態加某個字符之后會轉移到哪個狀態nxts,cnxt_{s,c}nxts,c?,然后設fi,sf_{i,s}fi,s?表示現在已經有iii個字符,dpdpdp數組狀態為jjj時的方案數,然后轉移就好了。
之后NOINOINOI那個限制多開一維來維護就好了,要滾動不然會炸。
時間復雜度O(2mn)O(2^mn)O(2mn),然后因為要判NOINOINOI所以常數比較大。
code
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int N=1100,M=16,P=1e9+7; const int d[3][3]={{1,0,0},{1,2,0},{1,0,3}}; int n,m,f[3][1<<M][3] ,ans[M]; int a[M],g[1<<M],h[1<<M],nxt[1<<M][3]; char s[M]; int ct(int x){int ans=0;while(x)x-=(x&-x),ans++;return ans; } int main() {scanf("%d%d",&n,&m);scanf("%s",s+1);for(int i=1;i<=m;i++){if(s[i]=='O')a[i]=1;if(s[i]=='I')a[i]=2;}int MS=(1<<m);for(int s=0;s<MS;s++){for(int i=1;i<=m;i++)g[i]=g[i-1]+((s>>i-1)&1);for(int c=0;c<3;c++){for(int i=1;i<=m;i++){h[i]=max(h[i-1],g[i]);if(a[i]==c)h[i]=max(h[i],g[i-1]+1);if(h[i]>h[i-1])nxt[s][c]|=(1<<i-1);}}}f[0][0][0]=1;for(int i=1;i<=n;i++){memset(f[i&1],0,sizeof(f[i&1]));for(int s=0;s<MS;s++){for(int t=0;t<3;t++){for(int c=0;c<3;c++){if(t==2&&c==2)continue;int z=d[t][c];(f[i&1][nxt[s][c]][z]+=f[~i&1][s][t])%=P;}}}}for(int s=0;s<MS;s++)for(int t=0;t<3;t++)(ans[ct(s)]+=f[n&1][s][t])%=P;for(int i=0;i<=m;i++)printf("%d\n",ans[i]);return 0; }總結
以上是生活随笔為你收集整理的P4590-[TJOI2018]游园会【dp套dp】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CF346E-Doodle Jump【类
- 下一篇: 电脑配置入门基本知识(电脑配置 入门)