DP套DP
DP套DP,就是将内层DP的结果作为外层DP的状态进行DP的方法。
[BZOJ3864]Hero meet devil
对做LCS的DP数组差分后状压,预处理出转移数组,然后直接转移即可。
tr[S][k]表示当前差分状压后的状态为S,加入字符k(k为ACGT中一个)后会转移到什么状态。
f[i][S]表示串已构造到第i位,和模式串的匹配状态差分后为S,的方案数。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 5 typedef long long ll; 6 using namespace std; 7 8 const int N=1010,M=50010,mod=1e9+7; 9 int T,n,m,d[20],g[20],ans[N],tr[M][4],cnt[M],f[2][M]; 10 char s[20],c[10]="ACGT"; 11 12 int main(){ 13 freopen("bzoj3864.in","r",stdin); 14 freopen("bzoj3864.out","w",stdout); 15 for (scanf("%d",&T); T--; ){ 16 scanf("%s%d",s+1,&m); n=strlen(s+1); 17 for (int S=0; S<1<<n; S++){ 18 if (S) cnt[S]=cnt[S^(S&-S)]+1; 19 rep(i,0,n-1) d[i+1]=d[i]+((S>>i)&1); 20 rep(k,0,3){ 21 rep(i,1,n) g[i]=max(max(d[i],g[i-1]),(c[k]==s[i])?d[i-1]+1:0); 22 tr[S][k]=0; 23 rep(i,0,n-1) if (g[i+1]-g[i]) tr[S][k]|=1<<i; 24 } 25 } 26 memset(ans,0,sizeof(ans)); memset(f,0,sizeof(f)); f[0][0]=1; 27 rep(i,1,m){ 28 int v=i&1,u=v^1; memset(f[v],0,sizeof(f[v])); 29 for (int S=0; S<1<<n; S++) 30 rep(k,0,3) f[v][tr[S][k]]=(f[v][tr[S][k]]+f[u][S])%mod; 31 } 32 for (int S=0; S<1<<n; S++) ans[cnt[S]]=(ans[cnt[S]]+f[m&1][S])%mod; 33 rep(i,0,n) printf("%d\n",ans[i]); 34 } 35 return 0; 36 }
[BZOJ5336][TJOI2018]party
同上题,多加一维0/1/2表示末尾和'NOI'匹配到第几位即可。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 5 typedef long long ll; 6 using namespace std; 7 8 const int N=1010,M=50010,mod=1e9+7; 9 int T,n,m,d[20],g[20],ans[N],tr[M][4],cnt[M],f[2][M][3]; 10 char s[20],c[10]="NOI"; 11 12 int main(){ 13 freopen("bzoj5336.in","r",stdin); 14 freopen("bzoj5336.out","w",stdout); 15 scanf("%d%d%s",&m,&n,s+1); 16 for (int S=0; S<1<<n; S++){ 17 if (S) cnt[S]=cnt[S^(S&-S)]+1; 18 rep(i,0,n-1) d[i+1]=d[i]+((S>>i)&1); 19 rep(k,0,2){ 20 rep(i,1,n) g[i]=max(max(d[i],g[i-1]),(c[k]==s[i])?d[i-1]+1:0); 21 tr[S][k]=0; 22 rep(i,0,n-1) if (g[i+1]-g[i]) tr[S][k]|=1<<i; 23 } 24 } 25 f[0][0][0]=1; 26 rep(i,1,m){ 27 int v=i&1,u=v^1; memset(f[v],0,sizeof(f[v])); 28 for (int S=0; S<1<<n; S++) rep(l,0,2) if (f[u][S][l]){ 29 rep(k,0,2){ 30 int S1=tr[S][k],l1=l; 31 l1=(k==l) ? l1+1 : ((!k)?1:0); 32 if (l1<3) f[v][S1][l1]=(f[v][S1][l1]+f[u][S][l])%mod; 33 } 34 } 35 } 36 for (int S=0; S<1<<n; S++) 37 rep(i,0,2) ans[cnt[S]]=(ans[cnt[S]]+f[m&1][S][i])%mod; 38 rep(i,0,n) printf("%d\n",ans[i]); 39 return 0; 40 }
[BZOJ3591]最长上升子序列
给出1~n的一个排列的一个最长上升子序列,求原排列可能的种类数。
考虑最长上升子序列$O(n\log n)$算法中使用的单调队列,将单调队列中的数组成的集合作为状态DP。
f[S]表示当前状态S的方案数,其中S是一个三进制数,第i位为0表示这个数尚未被选入数列,1表示选入数列但不在单调队列中,2表示在单调队列中。
对于每个状态,枚举下一个加入序列的数是什么来转移。可以先预处理减小常数。
具体细节可看代码注释,$O(2^n n^2+3^n n)$
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 5 using namespace std; 6 7 const int N=16,M=1.5e7; 8 int n,m,x,id[N],p[N],f[M]; 9 bool flag[1<<N][N]; 10 int v[1<<N][N],c[1<<N],mx[1<<N],s[1<<N],ans=0; 11 12 int main(){ 13 freopen("bzoj3591.in","r",stdin); 14 freopen("bzoj3591.out","w",stdout); 15 scanf("%d%d",&n,&m); 16 rep(i,1,m) scanf("%d",&x),id[x-1]=i; 17 p[0]=1; rep(i,1,n) p[i]=p[i-1]*3; 18 for (int S=0; S<(1<<n); S++)//预处理出每个可能的状态转移,S为当前已经选进单调队列的集合,flag表示能否转移,v是转移到什么位置 19 rep(i,0,n-1){ 20 if (S&(1<<i)){ c[S]+=p[i]; mx[S]=max(mx[S],i); s[S]++; continue; } 21 //c:全1的三进制状态,mx:最大数,s:popcount 22 if (!id[i]) flag[S][i]=1; 23 else{ 24 int tot=0; 25 rep(j,0,n-1) if (S&(1<<j) && id[j]) tot++; 26 if (tot+1==id[i]) flag[S][i]=1;//满足在LIS中的位置为id[i] 27 } 28 int t=-1,x=0; 29 rep(j,0,n-1) if (S&(1<<j)) x+=p[j];//S转三进制 30 for (int j=n-1; j>i; j--) if (S&(1<<j)) t=j;//找到集合中比i大的最小的数更新 31 if (~t) v[S][i]=x-p[t]+p[i]; else v[S][i]=x+p[i]; 32 } 33 f[0]=1; 34 for (int x=0; x<(1<<n); x++) 35 for (int y=x; y>0 || (!y && !x); (!x) ? y-- : y=(y-1)&x)//x为已在序列中的数的集合,y为在单调队列中数的集合 36 if (f[c[x]+c[y]]){ 37 int t=c[x]+c[y]; 38 for (int i=0; s[y]==m ? i<mx[y] : i<n; i++)//枚举新加进序列的数(若单调队列长度已满则不能加入更大的数) 39 if (flag[x][i]) f[c[x]+p[i]+v[y][i]]+=f[t];//新加进序列的数一定也加进单调队列 40 if (x==(1<<n)-1) ans+=f[t]; 41 } 42 printf("%d\n",ans); 43 return 0; 44 }