邮局 题解

题目
这一题是一道很不错的题目,也是我很喜欢的一道题目,对于区间DP这一题可以好好看看。
首先我们分析题目,对于这个题目应该会很有感觉,这就是区间DP,
我们要做到以下几点:
1、清楚状态转移方程的框架是什么?
2、清楚操作是什么?
3、计算出操作的花费
4、完善状态转移方程(即定下完整的状态转移方程)。
我们先解决第一点,框架是什么?首先解决状态定义的框架,我们肯定要以区间和操作次数来做为状态,对于区间我们考虑前面区间确定是否会影响到后面,如果不会我们便可以舍去左端点的那一维,直接默认为最左端,(因为通常都是要左端点和右端点两维的)。
我们再解析一下实质,对于这种题目,你肯定要学会抽象出来,否则你肯定不会写。
我们这道题可以说对于一个邮局,离它最近的村庄(也就是对答案产生的贡献)肯定是连续的一段(这也是我们采用区间DP的原因),这样我们就是通过DP去不断的通过最小这一原则去达到最优解的情况下,各个区间的分布(当然你只能计算出结果,而不能知道具体区间的信息,除非你记录),那么我们就相当于对于j个邮局,我们要去划分区间,所以我们就可以在原来的计算基础上去增加村庄的涉及范围和邮局个数,这样我们就知道如果定下了前面的,后面的一定不会受到影响,这样我们就可以定义出状态dp[i][j]表示前i个村庄中,建立j个邮局的费用。其状态转移方程框架为:dp[i][j]=min(dp[k][j-1]+cost[k+1][i]),1<=k<i,cost[i][j]就是在i到j中操作一次的花费。
下面分析一下操作,我们的操作其实就是建立一个邮局,而我们这个邮局影响的范围我们可以通过DP时枚举断点k来计算得到最优值,下面我们要做的就是如何计算i到j中的费用呢?
首先我们可以知道在i到j中,建立一个邮局,那么建立在最中间一定会最优,这个可以画图理解一下,接下来就是计算了,如果只是暴力枚举计算的话时间复杂度O(n^3),太大了,不行,我们考虑DP解决,我们通过分析,如果我们已经算出i到j-1的答案了,那么在这个基础上加一个j,即i到j的答案会有什么改变呢?
在这里插入图片描述
假设我们知道了1到6的答案,接下来我们看看和1到7有什么差别,1到6的中间点有两个,而1到7却只有一个,所以我们要分类讨论。
1、如果1到6的邮局建在4,那么加一个7,邮局不动,那么差别就是4到7这一段的距离。
2、如果1到6的邮局建在3,那么加一个7,邮局移动到4,这一个改变对1到6是没差别的,那么和7差别就是4到7这一段的距离。
我们发现无论哪种情况都是4到7这一段的距离,我们就可以得出状态转移方程,w[i][j]=w[i][j-1]+x[j]-x[(j+i)>>1],这样费用也就算出来了,这样我们整个状态转移方程就出来了,这一题也就解决了。
代码实现注意点:
1、题目并没有说村庄的排序有顺序,所以我们要先排一下序。
2、在DP中枚举邮局数量的循环一定要在第一重循环,对村庄数量的循环要在第二重,因为我们状态转移方程中要求我们要知道j-1才可以计算。
这样时间复杂度就是O(n^3)。
剩下的就是四边形不等式的常规优化了,在这里就不详细解释了,想了解的读者可以去博主资源库里边下载课件。
代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int x[3100],dp[3100][400],w[3100][3100],s[3100][3100];
bool cmp(int x,int y)//sort规定从小到大 
{
	return x<y;
}
int main()
{
	int v,p;
	memset(dp,0x3f,sizeof(dp));//初始化 
	scanf("%d %d",&v,&p);
	for(int i=1;i<=v;i++)
		scanf("%d",&x[i]);
	sort(x+1,x+1+v,cmp);//排序 
	for(int i=1;i<=v;i++)
		for(int j=i;j<=v;j++)
			w[i][j]=w[i][j-1]+x[j]-x[(j+i)>>1];//计算费用 
	for(int i=1;i<=v;i++)//DP边界 
		dp[i][1]=w[1][i];//可以知道如果j==1,那么就是w的值了 
	for(int i=1;i<=p+1;i++)
		s[v+1][i]=v;//这个是我们四边形不等式的优化数组 
	for(int i=1;i<=v;i++)
		s[i][1]=1;//赋值初值,上面的也是,原则是第一次DP一定要从头到尾枚举一次 
	for(int j=2;j<=p;j++)//先枚举邮局数量 
	{
		for(int i=v;i>=1;i--)//从大到小枚举,因为在四边形不等式中要求我们要先计算dp[i+1][j] 
		{
			for(int k=s[i][j-1];k<=s[i+1][j];k++)//四边形不等式常规操作 
			{
				if(dp[k][j-1]+w[k+1][i]<dp[i][j])//判断是否更优 
				{
					dp[i][j]=dp[k][j-1]+w[k+1][i];//计算DP值 
					s[i][j]=k;//缩小范围 
				}
			}
		}
	}
	printf("%d",dp[v][p]);//输出答案 
	return 0;
}

如果有读者不懂得或者有笔误的欢迎留言。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值