链接http://poj.org/problem?id=1160
很好的一个题,涉及到了以前老师说过的一个题目,可惜没往那上面想。
题意,给出N个城镇的地址,他们在一条直线上,现在要选择P个城镇建立邮局,使得每个城镇到离他最近的邮局距离的总和尽量小。
首先提一个这个问题的简化版本,如果P=1得话,这个距离是多少呢? 这个问题的解就是将这个唯一的邮局建在(l+r)/2的位置,答案就是最优解,
这个类似于中位数的概念,我们有一个数学归纳法简单的证明
数轴上有n个点,求到这n个点距离最小的一个点 问题描述:如题分析:这个题目不能简单地将所有点的平均数作为答案,这样是不对的,有反例。例如:x1=1, x2=3, x3=4, x4=100, 那么他们的平均数为x=27, 所有点到x的距离为146, 但当我们取x=10时,得打距离的值为116,比146更小。下面我们来用归纳法的思想来分析:如果n=1,那么就取这个点如果n=2,那么取x1和x2之间的任何一个点都是答案如果n=3,那么取中间的那个点,就是答案如果n=4,那么取中间的两个点所构成的闭区间就是答案... 如果n是奇数,那么就是第(n+1)/2这个点如果n是偶数,那么就是n/2这个点和(n/2)+1这两个点所构成的闭区间假设所有点都按序排列。
同理也可以推广到二维平面选点,只要找两次x和y的中位数就是最佳的地址。
对于此题不难想到dp[i][j]表示前i个城镇建立j个邮局的最小花费距离,因为不是每个地方都有邮局所以用dp[i][j]表示i-j之间的话不太方便,起点总是1所以可以免去一维。
则 dp[i][j]=MIN{dp[i][j],dp[k][j-1]+dis[k+1][i]| ,1<=k<=i} 其中dis[a][b]表示a-b之间建立一个邮局的最小花费距离,由上面的方法可以引出递推式:dis[i][j]=dis[i][j-1]+x[j]-x[(i+j)/2]
一开始我卡住,因为想不通递推式,我想这个分割点k如果k后面的邮局选址会影响到前面某个点使得价值更低这样岂不是会计算错误吗,后来想了想发现,这样会使结果变大,但是总会循环到那个最优的k使得恰好互不影响。因为离某个城镇的邮局最近的地方只有两种情况,左边或者右边,如果到k时出现交叉即
i_______k______j,从k分割时,(k,j]之间建立的那个邮局使得[i,k]的某个点会有更小的距离。一直向右循环总会到达使得两边互不影响的点,此时计算得到最优解。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define inf 0x3f3f3f3f 4 int dp[305][35]; 5 int x[305],one[305][305]={0}; 6 int main() 7 { 8 int N,M,i,j,k; 9 // freopen("in.txt","r",stdin); 10 while(cin>>N>>M){ 11 scanf("%d",&x[1]); 12 for(i=2;i<=N;++i) 13 scanf("%d",&x[i]); 14 memset(dp,inf,sizeof(dp)); 15 for(i=1;i<=N;++i) 16 for(j=i;j<=N;++j) 17 if(i==j) one[i][i]=0; 18 else 19 one[i][j]=one[i][j-1]+x[j]-x[(i+j)/2]; 20 for(i=1;i<=N;++i) dp[i][1]=one[1][i]; 21 for(i=2;i<=N;++i) 22 { 23 int up=min(M,i); 24 for(j=2;j<=up;++j) 25 { 26 for(k=1;k<=i;++k) 27 { 28 int s=dp[k][j-1]+one[k+1][i]; 29 if(dp[i][j]>s) dp[i][j]=s; 30 } 31 } 32 } 33 printf("%d\n",dp[N][M]); 34 } 35 return 0; 36 }
上面的渐进复杂度是O(N^2*M),对于此题的数据来说不必优化也能AC,但我们还是想这个可以利用四边形不等式优化吗?
在集训队毛子青的那篇<动态规划演算法的优化技巧>中明确指出,这道题目是可以的,我们在比赛时时没时间去证明也可以写一个朴素法来对拍。
这个题目不同于我们常见的区间dp,常见的区间dp是由小区间推导大区间,所以在计算dp[i][j]时,s[i][j-1]、s[i+1][j]都是[i,j]的子区间,在之前就计算出来了,
但是这个题目我们用dp[i][j]表示的区间是[1,i],在推导dp[i][j]时,s[i+1][j]还是未知的,我看了许多人写的博客都是将j倒置处理然后对s[i+1][j]附一个初值,我很不解为何非要这样,难道正方向推导不可行吗,研究了半天猜得出以上结论,他们那样处理就是因为不知道上界,所以将上界赋值为最大,知道了问题所在就简单了。
我们既然不知道s[i+1][j]只要令s[i+1][j]=i+1不就好了吗,下界得话我们已知,即使这样也能减小一些复杂度。
要时刻记住每个数组表示的意义才能准确的推导,s[i][j]表示的是k的范围所以最差情况就是[1,i];
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define inf 0x3f3f3f3f 4 int dp[305][305]; 5 int x[305],one[305][305]={0}; 6 int K[305][305]; 7 int main() 8 { 9 int N,M,i,j,k; 10 // freopen("in.txt","r",stdin); 11 while(cin>>N>>M){ 12 for(i=1;i<=N;++i) scanf("%d",&x[i]); 13 memset(dp,inf,sizeof(dp)); 14 for(i=1;i<=N;++i) 15 for(j=i;j<=N;++j) 16 if(i==j) one[i][i]=0; 17 else 18 one[i][j]=one[i][j-1]+x[j]-x[(i+j)/2]; 19 for(i=1;i<=N;++i) dp[i][1]=one[1][i]; 20 for(i=1;i<=N;++i) K[i][i]=1; 21 for(j=2;j<=M;++j) 22 { 23 for(i=1;i<=N;++i) 24 { 25 K[i+1][j]=i+1; 26 if(j>i) continue; 27 for(k=K[i][j-1];k<=K[i+1][j];++k) 28 { 29 int s=dp[k][j-1]+one[k+1][i]; 30 if(dp[i][j]>s) { 31 dp[i][j]=s; 32 K[i][j]=k; 33 } 34 } 35 } 36 } 37 printf("%d\n",dp[N][M]); 38 } 39 return 0; 40 }