C++解题报告:邮局(IOI 2000)—— 如何用平行四边形不等式巧妙优化DP

目录

题目描述

题目解析

思路详解

代码


邮局

传送门

题目描述

高速公路旁边有一些村庄。高速公路表示为整数轴,每个村庄的位置用单个整数坐标标识。没有两个在同样地方的村庄。两个位置之间的距离是其整数坐标差的绝对值。

邮局将建在一些,但不一定是所有的村庄中。为了建立邮局,应选择他们建造的位置,使每个村庄与其最近的邮局之间的距离总和最小。

你要编写一个程序,已知村庄的位置和邮局的数量,计算每个村庄和最近的邮局之间所有距离的最小可能的总和。

输入输出格式

输入格式:

第一行包含两个整数:第一个是村庄VV的数量,第二个是邮局的数量P,1≤P≤300,P≤V≤3000.

第二行包含V个整数。这些整数是村庄的位置。对于每个位置X,认为 ≤X≤10000。

输出格式:

第一行包含一个整数S,它是每个村庄与其最近的邮局之间的所有距离的总和。

输入输出样例

输入样例#1: 复制

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

输出样例#1: 复制

9

说明

对于40%的数据,V≤300


题目解析

拿到此题,很容易想到用dp来做,那怎么d?想怎么d怎么d首先用 dp[ i ][ j ] 表示在前 j 个村庄建造 i 个邮局,那么再用 w[ i ][ j ] 表示在第 i个村庄和第 j 个村庄之间建一个邮局的最小距离,然后大致思路就出来啦。就可以发展新村庄,摘掉贫困的帽子。。。

 


思路详解

对于 w[ i ][ j ] ,我们可以很快知道在第 i个村庄和第 j 个村庄的中点建造邮局其距离是最小的,就可以很快得出一个关于 w[ i ][ j ] 的动态转移方程,w[ i ][ j ] = w[ i ][ j - 1 ] + a[ j ] - a[ ( i + j ) / 2 ] ,就相当于在原来 w[ i ][ j - 1 ] 的基础上建立一个邮局,将会多出第 j 个村庄的距离和减少中点村庄(即建立了邮局的村庄)到邮局的距离

图片

有了 w[ i ][ j ] ,那么就有了半个瑰丽的人生了,再回过头来看 dp[ i ][ j ] , 循环一个 k , 表示前 k 个村庄,在前 k 个村庄建立 i-1 个邮局,然后就可以得出 dp 的动态转移方程 dp[ i ][ j ] = min( dp[ i ][ j ] , dp[ i - 1 ][ k ] + w[ k + 1 ][ j ] ) , dp[ i - 1 ][ k ]表示前 k-1 个村庄建立 i-1 个邮局的最小距离,加上在第 k+1个村庄与第 j 个村庄建立一个邮局的最小距离,即为 dp[ i ][ j ] 的最小距离。

有了所有的动态转移方程,这道题就结束了吗?当然!看一下数据,3000,不是很大,但循环三次,就很大了,3000的三次方不超时我直播吃,这里就引出主题——平行四边形不等式优化DP,如果对平行四边形不等式的性质不熟的话,就可以看一下浅谈平行四边形不等式优化DP这篇文章。

这时,因为 dp 的动态转移方程满足平行四边形不等式,就可以用 s[ i-1 ][ j ] 至 s[ i ][ j+1 ] 限制 k 的循环次数(因为它的最优解一定在这之中),从而减少一重循环(可以达到这个效果)


代码

结合代码理解消化啦

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#define ll long long
using namespace std;

ll v , p , a[3005]/*村庄距离*/ , w[3005][3005] , dp[3005][3005] , s[3005][3005];

int main()
{
	scanf("%lld%lld", &v , &p );
	for(int i = 1 ; i <= v ; ++ i )
		scanf("%lld", &a[i] );//初始化 
	sort( a + 1 , a + v + 1 );//要排序,因为输入的数据是无序的 
	for(int i = 1 ; i <= v ; ++ i )
	{
		for(int j = i ; j <= v ; ++ j )
		{
			w[i][j] = w[i][j - 1] + a[j] - a[(i + j) / 2];
			//计算在 i , j 之间建立一个邮局的最小距离 
		}
	}
	memset(dp , 0x3f , sizeof(dp));
	for(int i = 1 ; i <= v ; ++ i )
	{
		dp[1][i] = w[1][i];//初始化 dp  
	}
	for(int i = 2 ; i <= p ; ++ i )
	{
		s[i][v+1] = v ; //初始化 s  
		for(int j = v ; j >= i ; j -- )//从后往前 
		{
			for(int k = s[i-1][j] ; k <= s[i][j+1] ; k ++ )
			{
				if(dp[i][j] > dp[i-1][k] + w[k+1][j] )
				{
					dp[i][j] = dp[i-1][k] + w[k+1][j];
					s[i][j] = k ;//更新s[i][j] 
				}
			}
		}
	}
	printf("%lld", dp[p][v] );
}

猜你喜欢

转载自blog.csdn.net/qq_44013342/article/details/85611786