区间 DP 详解

区间动态规划(Interval dynamic programming,即区间 DP)常用来解决一段区间内的最优解。

区间 DP 类似于其他 DP,由多个子区间的答案合并为一个大区间的答案。

一般是先求出最小子区间的答案(如 d p i , i dp_{i,i} dpi,i),然后逐步进行合并得到最终答案。

区间 DP 一般可分为两种情况:

  1. 子区间 [ l , r ] [l,r] [l,r] 向周围两端扩散,一般其时间复杂度为 O ( n 2 ) O(n^2) O(n2)

    典型例题:P2758 编辑距离

    基本步骤:

    1. 枚举左端点 l l l
    2. 枚举右端点 r r r
    3. 根据状态转移方程计算从小区间转移到更大区间后的最优值。
  2. 多个小区间的答案合并为一个大区间,时间复杂度通常在 O ( n 3 ) O(n^3) O(n3) 及以上。

    典型例题:P1775 石子合并(弱化版)
    基本步骤:

    1. 枚举长度 l e n len len
    2. 枚举左端点 l l l
    3. 计算出右端点 r r r r = l + l e n − 1 r=l+len-1 r=l+len1)。
    4. 枚举区间的分割点 k k k,根据状态转移方程计算合并区间后的最优值。

例题

例题1:P2758 编辑距离

d p i , j dp_{i,j} dpi,j 表示在区间 [ l , r ] [l,r] [l,r] 中将 a i a_i ai 转化为 b i b_i bi 所消耗的最小代价。

  • 如果 s l ≠ s r s_l\ne s_r sl=sr
    d p l , r = max ⁡ { d p i − 1 , j + 1 , d p i , j − 1 + 1 , d p i − 1 , j − 1 + 1 , d p l , r } dp_{l,r}=\max\{dp_{i-1,j}+1,dp_{i,j-1}+1,dp_{i-1,j-1}+1,dp_{l,r}\} dpl,r=max{dpi1,j+1,dpi,j1+1,dpi1j1+1,dpl,r}

    如果 s l = s r s_l=s_r sl=sr
    d p l , r = max ⁡ ( d p l , r , d p l − 1 , r − 1 ) dp_{l,r}=\max(dp_{l,r},dp_{l-1,r-1}) dpl,r=max(dpl,r,dpl1,r1)

实现
#include<bits/stdc++.h>
using namespace std;
string s,s1;
int dp[2005][2005];
int main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>s>>s1;
	s=' '+s,s1=' '+s1;
	fill(dp[0],dp[2005],INT_MAX);
	for(int i=0;i<s.size();i++){
		dp[i][0]=i;
	}
	for(int j=0;j<s1.size();j++){
		dp[0][j]=j;
	}
	for(int i=1;i<s.size();i++){
		for(int j=1;j<s1.size();j++){
			dp[i][j]=min({dp[i-1][j]+1,dp[i][j-1]+1,dp[i-1][j-1]+1,dp[i][j],(s[i]==s1[j]?dp[i-1][j-1]:INT_MAX)});
		}
	}
	cout<<dp[s.size()-1][s1.size()-1];
	return 0;
}

例题2:CF2074G Game With Triangles: Season 2

d p l , r dp_{l,r} dpl,r 表示在区间 [ l , r ] [l,r] [l,r] 范围的点能凑出来的最大价值。

由于图形是一个环,因此我们需要断环为链。

接下来枚举区间 [ l , r ] [l,r] [l,r],状态转移:

d p l , r = max ⁡ { d p l , r , d p l + 1 , k − 1 + d p k + 1 , r − 1 + a l × a k × a r , d p l , k − 1 + d p k , r , d p l , k + d p k + 1 , r } dp_{l,r}=\max\{dp_{l,r},dp_{l+1,k-1}+dp_{k+1,r-1}+a_l\times a_k\times a_r,dp_{l,k-1}+dp_{k,r},dp_{l,k}+dp_{k+1,r}\} dpl,r=max{dpl,r,dpl+1,k1+dpk+1,r1+al×ak×ar,dpl,k1+dpk,r,dpl,k+dpk+1,r}

最后,答案为 max ⁡ { d p i , i + n − 1 } \max\{dp_{i,i+n-1}\} max{dpi,i+n1}

实现
#include<bits/stdc++.h>
using namespace std;
#define int long long
int t,n,a[805],dp[805][805];
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	for(cin>>t;t;t--){
		cin>>n;
		for(int i=1;i<=n;i++){
			cin>>a[i];
			a[i+n]=a[i];
		}
		fill(dp[0],dp[n*2+1],0);
		for(int len=2;len<=n;len++){
			for(int l=1;l<=n*2-len+1;l++){
				int r=l+len-1;
				for(int k=l+1;k<r;k++){
					dp[l][r]=max({dp[l][r],dp[l+1][k-1]+dp[k+1][r-1]+a[l]*a[k]*a[r],dp[l][k-1]+dp[k][r],dp[l][k]+dp[k+1][r]});
				}
			}
		}
		int ans=0;
		for(int i=1;i<=n;i++){
			ans=max(ans,dp[i][i+n-1]);
		}
		cout<<ans<<'\n';
	}
	return 0;
}

例题3:P1775 石子合并(弱化版)

d p l , r dp_{l,r} dpl,r 表示 [ l , r ] [l,r] [l,r] 范围内的石堆合并的最小代价。

由于石头堆围成一个环所以需要断环为链。

预处理出前缀和数组 s u m sum sum

状态转移(要么维持原状,要么几堆石子合并在一块):

d p l , r = min ⁡ ( d p l , r , d p l , k + d p k + 1 , r + s u m r − s u m l − 1 ) dp_{l,r}=\min(dp_{l,r},dp_{l,k}+dp_{k+1,r}+sum_r-sum_{l-1}) dpl,r=min(dpl,r,dpl,k+dpk+1,r+sumrsuml1)

实现
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,a[305],sum[305],dp[305][305];
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n;
	fill(dp[0],dp[n+1],1e18);
	for(int i=1;i<=n;i++){
		cin>>a[i];
		sum[i]=sum[i-1]+a[i],dp[i][i]=0;
	}
	for(int len=2;len<=n;len++){
		for(int l=1;l<=n-len+1;l++){
			int r=l+len-1;
			for(int k=l;k<r;k++){
				dp[l][r]=min(dp[l][r],dp[l][k]+dp[k+1][r]+sum[r]-sum[l-1]);
			}
		}
	}
	cout<<dp[1][n];
	return 0;
}

例题4:P2858 [USACO06FEB] Treats for the Cows G/S

用区间 DP 计算最大收益。

要么拿左边那个( a l a_l al),要么拿右边的( a r a_r ar)。

因此可以推出状态转移:

d p l , r = max ⁡ ( d p l + 1 , r + a l × ( n − l e n + 1 ) , d p l , r − 1 + a r × ( n − l e n + 1 ) ) dp_{l,r}=\max(dp_{l+1,r}+a_l\times(n-len+1),dp_{l,r-1}+a_r\times(n-len+1)) dpl,r=max(dpl+1,r+al×(nlen+1),dpl,r1+ar×(nlen+1))

实现
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,a[2005],dp[2005][2005];
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		dp[i][i]=a[i]*n;
	}
	for(int len=2;len<=n;len++){
		for(int l=1;l<=n-len+1;l++){
			int r=l+len-1;
			dp[l][r]=max(dp[l+1][r]+a[l]*(n-len+1),dp[l][r-1]+a[r]*(n-len+1));
		}
	}
	cout<<dp[1][n];
	return 0; 
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值