poj1160 post office

题目大意:有n个乡村,现在要建立m个邮局,邮局只能建在乡村里。现在要使每个乡村到离它最近的邮局距离的总和尽量小,求这个最小距离和。

n<300,p<30,乡村的位置不超过10000.

分析:这题是IOI的老题了,所以数据规模很小,朴素的DP也可以过。但作为四边形优化的题目也很不错。

设f[i][j]表示前i个乡村设j个邮局的最小距离和。

f[i][j]=min(f[k][j-1]+w(k+1,i)) 其中w(k+1,i)表示第i+1个乡村到第i个乡村到其中位点的距离之和。因为邮局肯定是建立这一段的中位点才能保证这一段的距离和最小。这个是很容易证明的,用反证法即可。

k作为决策点,如果在区间[1,i]中枚举,则总的事件复杂度为O(N^3)

但因为w(k+1,i)满足区间包含关系,同时满足平行四边形关系(证明貌似挺难)。所以f[i][j]也满足平行四边形原理。设s[i][j]表示f[i][j]的最佳决策点,则有

s[i-1][j]<=s[i][j]<=s[i][j+1]。  (1)

但实际上可以从常识角度直接推出上式(1)式。

如果村子数不变,邮局数增加,则邮局的起止范围应该往两边扩张,至少应保持不变;相反,若邮局数减少,则其起止范围应该往中间收缩,至少应保存不变。

所以s[i][j]<=s[i][j+1]。

所以,可以直接应用这个式子1了。

如果邮局数不变,村子数减少(最右边的村子剔除),则邮局应该往左边微调,不可能往右边移动;相反,则邮局应该往右微调。

所以s[i-1][j]<=s[i][j].

w(i,j)有O(1)的方法求出。我们首先预处理出所有乡村到最左边乡村的距离,并求前缀和,记为sum1,;再处理处所有乡村距离最右边的乡村的距离,并求后缀和,记为sum2。

设【i,j】的中位点为k,则w(i,j)=sum1[j]-sum1[k]-(j-k)*(pos[k]-pos[1])+sum2[i]-sum2[k]-(k-i)*(pos[n]-pos[k])

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #define MAXN 305
 5 using namespace std;
 6 int f[MAXN][MAXN],w[MAXN][MAXN],s[MAXN][MAXN],sum1[MAXN],sum2[MAXN];
 7 int pos[MAXN],n,p;
 8 void pre()
 9 {
10     for(int i=2;i<=n;i++)
11         sum1[i]=sum1[i-1]+pos[i]-pos[1];
12     for(int i=n-1;i>=1;i--)
13         sum2[i]=sum2[i+1]+pos[n]-pos[i];
14         for(int i=1;i<n;i++)
15             for(int j=i+1;j<=n;j++)
16             {
17             int k=(i+j)/2;
18     int res=0;
19     res+=sum1[j]-sum1[k]-(j-k)*(pos[k]-pos[1]);
20     res+=sum2[i]-sum2[k]-(k-i)*(pos[n]-pos[k]);
21     w[i][j]=res;
22         }
23 }
24 int main()
25 {
26     scanf("%d%d",&n,&p);
27     for(int i=1;i<=n;i++)
28         scanf("%d",&pos[i]);
29         pre();
30         memset(f,0x5f,sizeof f);
31     for(int i=1;i<=n;i++)
32         f[i][1]=w[1][i];
33     for(int i=1;i<=n;i++)
34         for(int j=min(i,p);j<=n;j++)
35         {
36             if(j>=i||j>p)s[i][j]=i;
37             }
38     for(int i=2;i<=n;i++)
39         for(int j=min(p,i);j>=1;j--)
40         {
41             if(i==j){f[i][j]=0,s[i][j]=j-1;continue;}
42             for(int k=s[i-1][j];k<=s[i][j+1];k++)
43                 if(f[i][j]>f[k][j-1]+w[k+1][i])
44                 {f[i][j]=f[k][j-1]+w[k+1][i];
45                 s[i][j]=k;
46                 }
47         }
48         printf("%d
",f[n][p]);
49     }
View Code
原文地址:https://www.cnblogs.com/hefenghhhh/p/4597843.html