【学习笔记】DP 套 DP
内外层 DP#
大致一个经典的形式是“求……值为……的方案数”,特点是对于一个给定的情况,我们需要 DP 求出其对应的值,而题目要求对于所有情况求出对应方案数。
换言之内层是求出值是多少或者判断是否合法的判定 DP,而外层是求方案数的计数 DP。
外层 DP 是在根据内层 DP 值建出的 DFA 上进行的。
一道题#
求与给定字符串的 长为 的方案数。
求 是一个经典 DP,设 为前 位与给定字符串 前 位的 ,转移方程:
发现关于 的一行只需要已知 以及关于 的一行即可,且 一行相邻两位只增加 或 ,可以把这个差分压成一个状态,还原成原数组后结合当前字符就可以得到下一行。
于是可以建出这个 DFA。
外层 DP 就是在 DFA 上进行的,设 表示长度为 的字符串匹配到状态 的方案数。
剩下一个限制增加一维特判即可。
点击查看代码
int n,m;
char S[20];
int mp[128];
int popcount[maxn];
int trans[maxn][3];
int pre[20],now[20];
int dp[2][maxn][3];
inline void build(){
for(int s=0;s<(1<<m);++s){
pre[0]=0;
for(int i=1;i<=m;++i) pre[i]=pre[i-1]+((s>>i-1)&1);
for(int c=0;c<3;++c){
now[0]=0;
for(int i=1;i<=m;++i){
now[i]=max(pre[i],now[i-1]);
if(mp[S[i]]==c) now[i]=max(now[i],pre[i-1]+1);
}
for(int i=1;i<=m;++i) trans[s][c]|=((now[i]-now[i-1])<<i-1);
}
}
}
int ans[20];
int main(){
mp['N']=0,mp['O']=1,mp['I']=2;
for(int i=1;i<(1<<15);++i) popcount[i]=popcount[i>>1]+(i&1);
n=read(),m=read();
scanf("%s",S+1);
build();
dp[0][0][0]=1;
for(int i=0;i<n;++i){
for(int s=0;s<(1<<m);++s){
for(int c=0;c<3;++c){
if(!c){
dp[i&1^1][trans[s][c]][1]=(dp[i&1^1][trans[s][c]][1]+dp[i&1][s][0])%mod;
dp[i&1^1][trans[s][c]][1]=(dp[i&1^1][trans[s][c]][1]+dp[i&1][s][1])%mod;
dp[i&1^1][trans[s][c]][1]=(dp[i&1^1][trans[s][c]][1]+dp[i&1][s][2])%mod;
}
else if(c==1){
dp[i&1^1][trans[s][c]][0]=(dp[i&1^1][trans[s][c]][0]+dp[i&1][s][0])%mod;
dp[i&1^1][trans[s][c]][2]=(dp[i&1^1][trans[s][c]][2]+dp[i&1][s][1])%mod;
dp[i&1^1][trans[s][c]][0]=(dp[i&1^1][trans[s][c]][0]+dp[i&1][s][2])%mod;
}
else{
dp[i&1^1][trans[s][c]][0]=(dp[i&1^1][trans[s][c]][0]+dp[i&1][s][0])%mod;
dp[i&1^1][trans[s][c]][0]=(dp[i&1^1][trans[s][c]][0]+dp[i&1][s][1])%mod;
}
}
}
for(int s=0;s<(1<<m);++s) dp[i&1][s][0]=dp[i&1][s][1]=dp[i&1][s][2]=0;
}
for(int s=0;s<(1<<m);++s){
for(int k=0;k<3;++k){
ans[popcount[s]]=(ans[popcount[s]]+dp[n&1][s][k])%mod;
}
}
for(int i=0;i<=m;++i) printf("%d\n",ans[i]);
return 0;
}
另一道题#
CodeForces-979E Kuro and Topological Parity *2400
内层 DP 写出来是:
取模之后 数组状态也有 ,很难像上一题操作,然而判断结果是否合法时是对 求一个前缀和,且连边并没有特殊要求,所以并不关心 的具体样子。
好路径为偶数的点连出的边是任意连的,好路径为奇数的点连出的边有奇偶性的限制,除此之外还有一个颜色的限制(同色不计入结果,因此同色任意连),于是只需要维护前 个点中有 个好路径为奇数的白点, 个好路径为奇数的黑点, 个好路径为偶数的白点,剩余的 个点是好路径为偶数的黑点,这样复杂度是 。
好路径为偶数的点对任意的颜色都是没有影响的,所以最后一维也可以略去,复杂度 。
所以 DP 套 DP 并不需要建出 DFA,只是需要在外层 DP 状态设计中表示出内层的当前状态。
参考资料#
作者:SoyTony
出处:https://www.cnblogs.com/SoyTony/p/Learning_Notes_about_DP_of_DP.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效