目录
题目描述
题目解析
思路详解
代码
邮局
题目描述
高速公路旁边有一些村庄。高速公路表示为整数轴,每个村庄的位置用单个整数坐标标识。没有两个在同样地方的村庄。两个位置之间的距离是其整数坐标差的绝对值。
邮局将建在一些,但不一定是所有的村庄中。为了建立邮局,应选择他们建造的位置,使每个村庄与其最近的邮局之间的距离总和最小。
你要编写一个程序,已知村庄的位置和邮局的数量,计算每个村庄和最近的邮局之间所有距离的最小可能的总和。
输入输出格式
输入格式:
第一行包含两个整数:第一个是村庄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] );
}