dp 总结 2
dp 总结 2
前情提要: dp 总结 1
区间 dp
当我们需要把数组按照子段分来分去怎么办?
尝试枚举每一个子段.
如何枚举每一个子段呢?
首先枚举子段长度, 再枚举子段起点, 然后从子段里寻找分割点来解决.
for(ll x=1;x<=n;x++) // 初始化
f[x][x]=c[x];
for(ll w=2;w<=n;w++) // 枚举子段长度
for(ll x=1;x+w-1<=n;x++){ // 枚举子段起点
ll y=x+w-1; // 得到子段终点
f[x][y]=init_ans; // 初始化
for(ll z=x+1;z<y;z++) // 枚举子段分割点
f[x][y]=better_ans(f[x][y],f[x][z]+f[z+1][y]);
}
很简单的区间 dp, 需要先预处理一下前缀和.
for(ll x=1;x<=n;x++)
f[x][x]=a[x],
a[x]+=a[x-1];
for(ll w=2;w<=n;w++)
for(ll x=1;x+w-1<=n;x++){
ll y=x+w-1;
f[x][y]=f[x][x]+(a[x]-a[x-1])+(f[x+1][y]+a[y]-a[x]);
for(ll z=x+1;z<y;z++)
f[x][y]=std::min(f[x][y],(f[x][z]+a[z]-a[x-1])+(f[z+1][y]+a[y]-a[z]));
} std::cout<<f[1][n]-a[n]<<"\n";
这就是经典的打气球问题.
我们按照规则, 取最大值转移即可.
for(ll w=2;w<=n+2;w++)
for(ll x=0;x+w<=n+2;x++){
ll y=x+w;
for(ll z=x+1;z<y;z++)
dp[x][y]=std::max(dp[x][y],dp[x][z]+dp[z][y]+c[x]*c[z]*c[y]);
} std::cout<<dp[0][n+1]<<"\n";
以及再难一点点.
我们说回文串怎么找, 就可以得到转移方程.
for(ll x=1;x<=n;x++) // 长度为 1 的子段, 一枪即可
f[x][x]=1;
for(ll x=1;x<n;x++) // 长度为 2 的子段, 如果相同就一枪, 否则两枪
f[x][x+1]=1+(s[x]!=s[x+1]);
for(ll w=3;w<=n;w++)
for(ll x=1;x+w-1<=n;x++){
ll y=x+w-1;
f[x][y]=f[x+1][y-1]+(s[x]!=s[y])*2; // 是否能在上一枪一同被打掉
for(ll z=x;z<y;z++)
f[x][y]=std::min(f[x][y],f[x][z]+f[z+1][y]);
} std::cout<<f[1][n]<<"\n";
时间复杂度
可以发现, 区间 dp 需要枚举区间长度 \(w\) 和区间起点 \(x\), 然后再转移, 于是时间复杂度是 \(O(n^{2}k)\), 其中 \(k\) 为转移复杂度.
状压 dp
状压应该都知道吧, 把一个集合变成一个数字, \(2^{n}-1\) 即全集, \(0\) 即空集, \(s\&2^{x}\) 说明 \(x\in S\).
那么我们有两种状压 dp.
枚举元素
经典的旅行商问题, 考虑记录 \(f_{s,x}\) 表示经过了 \(s\) 里所有顶点并且停在顶点 \(x\) 的最小距离, 于是可以写出代码.
std::fill(f[0],f[0]+(1l<<MaxN)*MaxN,inf);
f[1][0]=0;
for(ll s=1;s<1l<<N;s++)
for(ll x=0;x<N;x++) if(s&1l<<x)
for(ll y=0;y<N;y++) if((s&1l<<y)==0)
f[s|1l<<y][y]=std::min(f[s|1l<<y][y],f[s][x]+g[x][y]);
ll fusu=inf;
for(ll x=0;x<N;x++)
fusu=std::min(fusu,f[(1l<<N)-1][x]+g[x][0]);
std::cout<<fusu<<"\n";
可以看出, 数组大小是 \(2^{n}n\) 而转移是 \(O(n)\), 于是时间复杂度就是 \(O(2^{n}n^{2})\).
枚举子集
这个没有例题, 最起码我没找到.
求子集和.
for(ll x=0;x<n;x++)
f[1l<<x]=a[x];
for(ll s=1;s<1l<<n;s++)
for(ll t=s;t;t=(t-1)&s) // 枚举子集
f[s]=f[t]+f[s^t];
std::cout<<f[(1l<<n)-1]<<"\n";
对于 \(x\) 有 \(4\) 种情况:
- \(x\in s\land x\in t\)
- \(x\in s\land x\notin t\)
- \(x\notin s\land x\in t\) 显然, 由于 \(t\subset s\) 所以这种情况是不存在的
- \(x\notin s\land x\notin t\)
于是只有 \(3\) 种情况, 则时间复杂度为 \(O(3^{n})\).
下期预告
树形 dp.
原文来自CnBlogs, 作者: young_tea.

浙公网安备 33010602011771号