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]);
}

problem:AtCoder-dp_n

很简单的区间 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";

problem:洛谷-U600603

这就是经典的打气球问题.

我们按照规则, 取最大值转移即可.

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";

以及再难一点点.

problem:CodeForces-607B

我们说回文串怎么找, 就可以得到转移方程.

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.

枚举元素

problem:洛谷-U600611

经典的旅行商问题, 考虑记录 \(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\) 种情况:

  1. \(x\in s\land x\in t\)
  2. \(x\in s\land x\notin t\)
  3. \(x\notin s\land x\in t\) 显然, 由于 \(t\subset s\) 所以这种情况是不存在的
  4. \(x\notin s\land x\notin t\)

于是只有 \(3\) 种情况, 则时间复杂度为 \(O(3^{n})\).

下期预告

树形 dp.

posted @ 2025-12-04 23:58  young_tea  阅读(3)  评论(0)    收藏  举报