DP经典题

http://poj.org/problem?id=1160

题意:有N个村庄,要在这N个村庄中建K个邮局,求如何布置这些邮局能使得所有村庄到离它最近的那个邮局的距离总和最小。

此题个人认为与最长递增子序列有一定的类似,我正是受了最长递增子序列解题方法的启发,才A出了此题,但貌似不能通过优化最长递增子序列的方法(nlgn)来优化它

///v个村庄,p个邮局,把邮局建在哪些村庄里才能使所有村庄到离家最近的邮局的总路径最短
///
#include <iostream>
#include <algorithm>
#define MAX 300       //村庄的最大个数
using namespace std;
int vill[MAX+10];    //每个村庄的坐标,严格递增数组
int dp[MAX+10][32];  //dp[i][j] 表示最后一个邮局在村庄 i ,一共建了 j 个邮局
int INF=4000000;
int	m[32];           //m[j] 表示邮局总数为 j 时最短距离

//二分查找,查找到两个村庄之间与它们距离相等的村庄
int binarySearch(int s,int e,int data){
	if(s<=e){
		int mid=(s+e)/2;
		if(vill[mid]==data)
			return mid;
		if(vill[mid]<data)
			s=mid+1;		
		else e=mid-1;
		return binarySearch(s,e,data);
	}
}
int main()
{

     int v,p;
     scanf("%d%d",&v,&p);
	 int i,j,k;
	 for(i=0;i<v;i++){
		 scanf("%d",&vill[i]);
	 }
	 memset(dp,0,sizeof(dp));

	 //初始化只建一个邮局时,该邮局建在各个村庄时的总路径
	 for(i=0;i<v;i++){
		 dp[i][1]=0;
		 for(k=0;k<i;k++){
			dp[i][1]+=vill[i]-vill[k];
		 }
		 for(k=i;k<v;k++){
			dp[i][1]+=vill[k]-vill[i];
		 }
	 }
	 for(i=1;i<v;i++){
			 for(k=0;k<=p;k++)
				 m[k]=INF;
			 for(k=0;k<i;k++){
				 int mid=binarySearch(k,i,(vill[i]-vill[k])/2);				 
				 int sum=0;			 				 
				 while(vill[mid]-vill[k]<=vill[i]-vill[mid])
					 mid+=1;	
                 //求出村庄 i 和 k 之间的村庄到邮局的总路径因为在 i 新建了一个邮局后的改变量
				 while(mid!=i){
					 sum-=2*vill[mid]-vill[k]-vill[i];
					 mid++;
				 }

				 //求出村庄 i 之后的村庄到邮局的总路径因为在 i 新建了一个邮局后的改变量,为负
				 sum-=(vill[i]-vill[k])*(v-i);
                 //更新 m 数组
				 for(j=1;j<=k+1&&j<=p;j++){
					 if(dp[k][j]+sum<m[j])
						 m[j]=dp[k][j]+sum;
				 }
			 }
			 //状态转移方程 dp[i][k]=min{dp[j][k],j<i}
			 for(k=2;k<=i+1&&k<=p;k++)
				 dp[i][k]=m[k-1];
			 
	 }
	 int minn=INF;
	 for(i=p-1;i<v;i++){
		 if(minn>dp[i][p])
		  minn=dp[i][p];
	 }
	 cout<<minn<<endl;
	 return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值