DP-区间DP

区间DP
区间dp,顾名思义,在区间上dp,大多数题目的状态都是由区间(类似于dp[l][r]这种形式)构成的,就是我们可以把大区间转化成小区间来处理,然后对小区间处理后再回溯的求出大区间的值,主要的方法有两种,记忆化搜索和递推。

在用递推来求解时,关键在于递推是for循环里面的顺序,以及dp的关键:状态转移方程。

当然大部分的区间dp都是有特点的,我们可以考虑符合什么条件下,大区间可以转化成小区间,然后找出边界条件,进行dp求解。


memset(dp,0,sizeof(dp))//初始dp数组
for(int len=2;len<=n;len++){//枚举区间长度
    for(int i=1;i<n;++i){//枚举区间的起点
        int j=i+len-1;//根据起点和长度得出终点
        if(j>n) break;//符合条件的终点
        for(int k=i;k<=j;++k)//枚举最优分割点
            dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+w[i][j]);//状态转移方程
    }
}

这时候就要用到一个经典的优化可以把它优化到:O(n^2),其实证明很难理解,但是大部分题都不会卡,因为dp已经很难了。

就算需要四边形优化,也就是多开一个数组s的事,在枚举最优分割点时,再缩小一下枚举范围,经典的用空间换时间的做法。

下面抛出基于四边形优化的代码。

for(int len=2;len<=n;len++){
    for(int i = 1;i<=n;i++){
	int j = i+len-1;
	if(j>n) break;
	for(int k = s[i][j-1];k<=s[i+1][j];k++){
	    if(dp[i][j]>dp[i][k]+dp[k+1][j]+w[i][j]){
		dp[i][j]=dp[i][k]+dp[k+1][j]+w[i][j];
		s[i][j]=k;
	    }
	}
    }
}

附上一些典例典例
poj1160
有一条直线公路,沿公路旁有村庄。高速公路用整数轴表示,每个村庄的位置用一个整数坐标标识。没有两个村庄处于同一位置。两个位置之间的距离是其整数坐标之差的绝对值。
邮局将在部分村庄(但不一定是全部村庄)中建立。一个村庄和其中的邮局位置相同。在建造邮局时,应选择其位置,以使每个村庄与其最近的邮局之间的所有距离的总和最小。
您将要编写一个程序,该程序会根据村庄的位置和邮局的数量,计算出每个村庄与其最近的邮局之间的所有距离中的最小和。

//经典动态规划
//dp(i,j)表示用i个邮局,从1到j村庄的最优解
//dis(i,j)表示只用1个邮局从i到j村庄的最优解,显然取i,j中点的村庄作为邮局点是最优的
//动态转移方程:dp(i,j) = min{dp(i-1,k) + dis(k+1,j)}(i <= k <= j-1)
//注意dp数组的初始化
#include<iostream>
using namespace std;
int d[305];
int dp[35][305];
int dis[305][305];
int n,m;
void cal_dis()
{
	for(int i=1;i<=n;i++)
	{
		for(int j=i+1;j<=n;j++)
		{
			int mid=(i+j)/2;
			for(int k=i;k<=j;k++)
			{
				dis[i][j]+=abs(d[k]-d[mid]);
			}
		}
	}
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>d[i];
	}
	cal_dis();
	for(int i=1;i<=n;i++)
	{
		dp[1][i]=dis[1][i];
	}
	for(int i = 2;i <= m;++i)
		for(int j = 1;j <= n;++j)
			dp[i][j] = 1e9;//初始化
	for(int i=2;i<=m;i++)
	{
		for(int j=i;j<=n;j++)
		{
			for(int k=i-1;k<j;k++)
			{
				dp[i][j]=min(dp[i][j],dp[i-1][k]+dis[k+1][j]);
			}
		}
	}
	cout<<dp[m][n]<<endl;
}

合并石子
在一个圆形操场的四周摆放 N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。
试设计出一个算法,计算出将 N 堆石子合并成 1 堆的最小得分和最大得分。

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int dpmax[300][300];//dp[i][j]表示合成i到j堆所需要的最大得分
int dpmin[300][300];
int sum[300];
int d[300];
int main()
{
	int n;
	cin>>n;
	memset(dpmax,0,sizeof(dpmax));
	for(int i=1;i<=n+n-1;i++)//注意是环,所以还要改成n+n-1
	{
		for(int j=i;j<=n+n-1;j++)
		{
			if(i==j) dpmin[i][j]=0;//注意dpmin的初始化!!
			else dpmin[i][j]=1e6;
		}
	}
	//sum[i]表示到i这个点一共的石子数
	for(int i=1;i<=n;i++)
	{
		cin>>d[i];
		sum[i]=sum[i-1]+d[i];
	}
	for(int i=n+1;i<n+n;i++)
	{
		sum[i]=sum[i-1]+d[i-n];
	}
	//区间dp模板
	int j;
	for(int len=2;len<=n+n;len++)
	{
		for(int i=1;i<n+n;i++)
		{
			j=i+len-1;
			if(j>=n+n) break;
			for(int k=i;k<j;k++)//注意不要等于
			{
				dpmax[i][j]=max(dpmax[i][j],dpmax[i][k]+dpmax[k+1][j]+sum[j]-sum[i-1]);
				dpmin[i][j]=min(dpmin[i][j],dpmin[i][k]+dpmin[k+1][j]+sum[j]-sum[i-1]);
			}
		}
	}
	int ansmax=0;
	int ansmin=1e9;
	for(int i=1;i<=n;i++)//因为是环所以截取所有1-n区间求最大最小
	{
		ansmax=max(ansmax,dpmax[i][i+n-1]);
		ansmin=min(ansmin,dpmin[i][i+n-1]);
	}
	cout<<ansmin<<endl;
	cout<<ansmax<<endl;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
区间DP是一种动态规划的方法,用于解决区间范围内的问题。在Codeforces竞赛中,区间DP经常被用于解决一些复杂的字符串或序列相关的问题。 在区间DP中,dp[i][j]表示第一个序列前i个元素和第二个序列前j个元素的最优解。具体的转移方程会根据具体的问题而变化,但是通常会涉及到比较两个序列的元素是否相等,然后根据不同的情况进行状态转移。 对于区间长度为1的情况,可以先进行初始化,然后再通过枚举区间长度和区间左端点,计算出dp[i][j]的值。 以下是一个示例代码,展示了如何使用区间DP来解决一个字符串匹配的问题: #include <cstdio> #include <cstring> #include <string> #include <iostream> #include <algorithm> using namespace std; const int maxn=510; const int inf=0x3f3f3f3f; int n,dp[maxn][maxn]; char s[maxn]; int main() { scanf("%d", &n); scanf("%s", s + 1); for(int i = 1; i <= n; i++) dp[i][i] = 1; for(int i = 1; i <= n; i++) { if(s[i] == s[i - 1]) dp[i][i - 1] = 1; else dp[i][i - 1] = 2; } for(int len = 3; len <= n; len++) { int r; for(int l = 1; l + len - 1 <= n; l++) { r = l + len - 1; dp[l][r] = inf; if(s[l] == s[r]) dp[l][r] = min(dp[l + 1][r], dp[l][r - 1]); else { for(int k = l; k <= r; k++) { dp[l][r] = min(dp[l][r], dp[l][k] + dp[k + 1][r]); } } } } printf("%d\n", dp[n]); return 0; } 希望这个例子能帮助你理解区间DP的基本思想和应用方法。如果你还有其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值