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\n",f[n][p]);

49     }
View Code

 

你可能感兴趣的:(Office)