邮局加强版(dp + 四边形不等式优化)

邮局加强版(dp + 四边形不等式优化)

目录

目录

邮局加强版(dp + 四边形不等式优化)

目录

题目

Sample Input

Sample Output

朴素解法

sum求法(一)

sum求法(二)

朴素代码

四边形不等式优化

正解代码


 

题目

There is a straight highway with villages alongside the highway. The highway is represented as an integer axis, and the position of each village is identified with a single integer coordinate. There are no two villages in the same position. The distance between two positions is the absolute value of the difference of their integer coordinates. 

Post offices will be built in some, but not necessarily all of the villages. A village and the post office in it have the same position. For building the post offices, their positions should be chosen so that the total sum of all distances between each village and its nearest post office is minimum. 

You are to write a program which, given the positions of the villages and the number of post offices, computes the least possible sum of all distances between each village and its nearest post office. 

Input

Your program is to read from standard input. The first line contains two integers: the first is the number of villages V, 1 <= V <= 300, and the second is the number of post offices P, 1 <= P <= 30, P <= V. The second line contains V integers in increasing order. These V integers are the positions of the villages. For each position X it holds that 1 <= X <= 10000.

Output

The first line contains one integer S, which is the sum of all distances between each village and its nearest post office.

Sample Input

10 5
1 2 3 6 7 9 11 22 44 50

Sample Output

9

朴素解法

f[i][j] : 前 i 个村庄安 j 个邮局的最小距离

由此我们可以得出状态转移方程

f[i][j] = min(f[i][j],f[k][j - 1] + sum[k + 1][i]);

我们将 f[k][j - 1] + sum[k + 1][i]; 提出来看

他的意思就是说我们将 j - 1 个邮局放到前 个村庄中,将第 j 个邮局放到 k + 1 到 i  个村庄中的任意一个,所以这里的 sum 的意思就是

sum[i][j] : i 至 j 村庄到 某一村庄的最小距离

而我们由中位数原理可得 k = (i + j) / 2 这个位置一定是最优的

由此我们只需要预处理出 sum 即可

sum求法(一)

直接 for 循环暴搜(太费时,但能 AC )

inline int abs(int x){
	return x < 0 ? -1 * x : x;
}
int dis(int x,int y){
	int mid = (x + y) / 2;
	int su = 0;
	for (reg int i = x; i <= y; ++ i)
		su += abs(a[i] - a[mid]);
	return su;
}

sum求法(二)

这种方法相较于第一种方法实际上就是用 空间换时间

首先我们需要两个数组 sum1 sum2

sum1[i] :从 1 至  i 号村庄到第 1 号村庄的距离

sum2[i]:从n 至 i 号村庄到第 n 号村庄的距离

然后我们借助一幅图

现在我们先用 sum1[j] - sum1[mid] 是不是就是(j - mid)这么多个村庄到 1 号村庄的距离呢,此时我们只需要将 1 - mid 这一部分减掉不就是 mid - j 个村庄到 mid 的距离呢?(sum1[j] - sum1[mid] - (j - mid) * (a[j] - a[mid])

这时我们还差 i - mid 这几个村庄到 mid 的距离,用 sum2 以同样的道理即可求出 (sum2[i] - sum2[mid] - (mid - i) * (a[n] - a[mid])

朴素代码

#include<cstdio>
#include<cstring>
#define reg register
#define M 300 + 5
#define N 30 + 5

int a[M],f[M][N],sum[M][M];
int n,m;

inline int min(int x,int y){
	return x < y ? x : y;
}
inline int abs(int x){
	return x < 0 ? -1 * x : x;
}
int dis(int x,int y){
	int mid = (x + y) / 2;
	int su = 0;
	for (reg int i = x; i <= y; ++ i)
		su += abs(a[i] - a[mid]);
	return su;
}
int main(){
	scanf("%d%d",&n,&m);
	for (reg int i = 1;i <= n; ++ i)
		scanf("%d",&a[i]);
	memset(f,127,sizeof(f));
	for (reg int i = 1;i <= n; ++ i)
		for (reg int j = i + 1;j <= n; ++ j)
				sum[i][j] = dis(i,j);
	for (reg int i = 1;i <= n; ++ i)
		f[i][1] = sum[1][i];
	for (reg int i = 1;i <= n; ++ i)
		for (reg int j = 2;j <= min(i,m); ++ j)
			for (reg int k = j - 1;k <= i - 1; ++ k)
				f[i][j] = min(f[i][j],f[k][j - 1] + sum[k + 1][i]);
	printf("%d\n",f[n][m]);
	return 0;
}

四边形不等式优化

首先我们得了解什么是四边形不等式优化(如果还不懂的话,我们可以借助伟大的百度或者知乎)

然后请看图

所以

w[i][i'] + w[j][j'] <= w[i][j'] + w[i'][j] 的由来

而你又会看到这个东西 s[i][j] <= s[i][j + 1] <= s[i + 1][j + 1]

然后你会神奇的发现他可以被改变

s[i][j - 1] <= s[i][j] <= s[i + 1][j]

s[i][j - 1] <= s[i + 1][j]

s[i - 1][j - 1 + 1] <= s[i + 1 - 1][j + 1] 

s[i - 1][j] <= s[i][j] <= s[i][j + 1]

所以我们就可以以此求出状态转移方程中的 最优 k 的范围 (s[i - 1][j] <= k <= s[i][j + 1])

正解代码

#include<cstdio>
#include<cstring>
#define reg register
#define M 300 + 5
#define N 30 + 5

int a[M],f[M][N],sum[M][M],s[M][M],su1[M],su2[M];
int n,m;

inline int min(int x,int y){
	return x < y ? x : y;
}
inline int abs(int x){
	return x < 0 ? -1 * x : x;
}
int main(){
	scanf("%d%d",&n,&m);
	for (reg int i = 1;i <= n; ++ i)
		scanf("%d",&a[i]);
	for (reg int i = 1;i <= n; ++ i)
		su1[i] = su1[i - 1] + a[i] - a[1];
	for (reg int i = n;i >= 1; -- i)
		su2[i] = su2[i + 1] + a[n] - a[i];
	memset(f,127,sizeof(f));
	for (reg int i = 1;i <= n; ++ i)
		for (reg int j = i + 1;j <= n; ++ j){
			int mid = (i + j) / 2;
			sum[i][j] = su1[j] - su1[mid] - (j - mid) * (a[mid] - a[1]) + su2[i] - su2[mid] - (mid - i) * (a[n] - a[mid]);
		}
	f[0][0] = f[1][1] = 0;
	for (reg int i = 1;i <= n; ++ i){
		for (reg int j = min(m,i);j >= 1; -- j){
			if ( ! s[i - 1][j])
				s[i - 1][j] = min(i - 1,j - 1);
			if ( ! s[i][j + 1])
				s[i][j + 1] = i - 1;
			for (reg int k = s[i - 1][j];k <= s[i][j + 1]; ++ k)
				if (f[i][j] > f[k][j - 1] + sum[k + 1][i]){
					f[i][j] = f[k][j - 1] + sum[k + 1][i];
					s[i][j] = k;
				}
		}
	}
	printf("%d\n",f[n][m]);
	return 0;
}

解释一下:

因为  k 的值是根据 i - 1 和 j + 1 来确定的,所以我们需要知道 i - 1 和 j + 1,即 i 顺推,j 逆推

然后我们为了保证答案的最优性,需要尽量扩大 k 的搜索范围(建立在 s[i - 1][j] 和 s[i][j + 1] 未赋值的情况下)

if ( ! s[i - 1][j])
	s[i - 1][j] = min(i - 1,j - 1);
if ( ! s[i][j + 1])
	s[i][j + 1] = i - 1;

注意看第二个 if 没有 max ,很奇怪吧,其实我们分情况讨论一下就可以证明他 一定是最大的了

  1. i <= m 所以 j 的最大情况也只是等于 i ,这时 i - 1最多就是等于  j - 1
  2. i > m 所以 j 的最大情况是 m,这时 i - 1 一定大于 j - 1

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值