dp 套 dp 学习笔记
所谓 dp 套 dp就是指这样一类题:给定 dp 结果,问有多少种输入能够导致这个结果,通常来说,这种问题可以转化为在自动机上 dp,内层 dp 的转移就是在自动机上转移。
这样说肯定是会有些笼统,我们考虑下面这一道例题。
例题
如果是一个求 LCS 的 dp 的话,转移式是这样的:
\[f_{i,j}=\begin{cases}
f_{i-1,j-1}+1(s_i=t_j)\\
\max\{ f_{i-1,j-1},f_{i-1,j},f_{i,j-1} \}
\end{cases}
\]
我们考虑 dp 的第二维只有 15 个,且相邻两项之间相差最多为 \(1\),所以我们可以设状态 \(F_{i,S}\) 表示另一个字符串长度为 \(i\) LCS 为 \(S\) 的方案数有哪些,我们只需要处理处每当我们往后加一个字符后 \(S\) 是怎么变化的就可以进行转移。具体看代码实现。
那么我们发现,这个东西内层的 \(S\to T\) 就相当于在自动机上转移,所以 dp 套 dp 的是指是在自动机上 dp。
代码:
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 1010
#define M 40010
using namespace std;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
template<typename T> inline T Max(T a,T b){return a<b?b:a;}
int n,m,t,lim,f[N],g[N],h[M][4],ans[N],F[N][M],b[N];
char s[N];
inline int ID(char c){
if(c=='A') return 0;else if(c=='C') return 1;else if(c=='G') return 2;else if(c=='T') return 3;
assert(0);return -1;
}
inline char InvID(int x){
if(x==0) return 'A';else if(x==1) return 'C';else if(x==2) return 'G';else if(x==3) return 'T';
assert(0);return -1;
}
inline void Init(){
scanf("%s",s+1);read(m);n=strlen(s+1);lim=(1<<n)-1;
}
inline int TRANS(int S,char c){
for(int i=0;i<=n-1;i++) f[i+1]=(S>>i)&1;
for(int i=1;i<=n;i++) f[i]+=f[i-1];
for(int i=1;i<=n;i++){
if(s[i]==c){
g[i]=f[i-1]+1;
}
else g[i]=Max(g[i-1],Max(f[i],f[i-1]));
}
for(int i=1;i<=n;i++) b[i]=g[i];
for(int i=1;i<=n;i++) g[i]=g[i]-b[i-1];
int now=0;
for(int i=1;i<=n;i++) if(g[i]) now|=(1<<(i-1));
return now;
}
inline void GetPre(){
for(int i=0;i<=lim;i++){
for(int j=0;j<=3;j++){
h[i][j]=TRANS(i,InvID(j));
// printf("h[%d][%d]=%d\n",i,j,h[i][j]);
}
}
}
inline void Solve(){
F[0][0]=1;
for(int i=0;i<=m-1;i++){
for(int j=0;j<=lim;j++){
// printf("F[%d][%d]=%d\n",i,j,F[i][j]);
if(!F[i][j]) continue;
for(int k=0;k<=3;k++){
(F[i+1][h[j][k]]+=F[i][j])%=mod;
}
}
}
for(int i=0;i<=lim;i++){
// printf("F[%d][%d]=%d\n",m,i,F[m][i]);
(ans[__builtin_popcount(i)]+=F[m][i])%=mod;
}
}
inline void Print(){
for(int i=0;i<=n;i++){
printf("%d\n",ans[i]);
}
}
inline void Clear(){
fill(ans,ans+n+1,0);
memset(F,0,sizeof(F));
}
int main(){
// freopen("my.in","r",stdin);
// freopen("my.out","w",stdout);
read(t);
while(t--){
Init();GetPre();Solve();Print();Clear();
}
return 0;
}
以上代码出现错误总结如下:
- g 忘记差分
- 变量名用混
- 忘记清空数组。
总结
我们考虑 dp 套 dp 这些题的通性,dp 套 dp 分为两个 dp:内层 dp 和外层 dp,在题目中,内层 dp 的转移通常作为自动机上的转移,而外层 dp 是我们的主题,通常运用状压差分等技巧简化状态。