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
",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
",ans[i]);
39     return 0;
40 }

[BZOJ3591]最长上升子序列

给出1~n的一个排列的一个最长上升子序列,求原排列可能的种类数。

考虑最长上升子序列$O(nlog 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
",ans);
43     return 0;
44 }

 

原文地址:https://www.cnblogs.com/HocRiser/p/9722577.html