题目链接:P4767 [IOI2000]邮局
题目大意:
整数轴上有 (V) 个村庄,要在其中一些村庄中建 (P) 个邮局,求每个村庄和最近的邮局之间所有距离的总和的最小值。
(1leq Pleq 300, Pleq Vleq 3000)
思路:
记 (val(i,j)) 为在 ([j,i]) 区间上建一个邮局后该区间距离和的最小值,设 (dp[i][j]) 为在 ([1,j]) 上建了 (i) 个邮局时的答案,有朴素 (O(V^2P)) 转移:
[dp[i][j]=min_{0leq k<j}{dp[i-1][k]+val(j,k+1)}
]
计算 (val(i,j)) :,记 (s[i]) 为前 (i) 个村庄坐标和,即 (s[i]=sum_{j=1}^i{a[j]}) ,根据中位数的性质,显然邮局建在中位数处是最优的,所以记 (mid=(i+j)/2) ,有:
[val(i,j)=s[i]-s[mid]-(i-mid)*a[mid]-(s[mid-1]-s[j-1]-(mid-j)*s[mid])\=s[i]+s[j-1]-2*s[mid]-(2*mid-i-j-1)*a[mid]
]
考虑将转移优化,(val(i,j)) 具有满足四边形不等式的潜质,对于 (val(i,j+1)) 和 (val(i+1,j)) ,两者邮局建立的位置相同,设为 (mid) ,可以得到:
[val(i,j+1)+val(i+1,j)=2*val(i,j+1)-(a[i+1]-a[mid])-(a[mid]-a[j])
]
[quadecause val(i,j)leq val(i,j+1)+(a[mid]-a[j]),quad val(i+1,j+1)leq val(i,j+1)+(a[i]-a[mid])
]
两式相加可得:
[val(i,j+1)+val(i+1,j)geq val(i,j)+val(i+1,j+1)
]
又因为显然 (val(i+1,j)geq val(i,j+1)) ,所以 (dp[i][j]) 满足二维决策单调性,即 (p[i][j-1]leq p[i,j]leq p[i+1][j]) ,其中 (p[i][j]) 为 (dp[i][j]) 的决策点 (k) ,这样我们就将时间复杂度降到了 (O(sum_{i=1}^Psum_{j=1}^V(p[i+1][j]-p[i][j-1]))=O(sum_{i=1}^P(p[i+1][V]-p[1][V-i]+V-i))=O(VP)) ,可以轻松通过此题。
实现细节:
- 由于转移 (dp[i][j]) 时要用到 (dp[i][j-1]) 和 (dp[i+1][j]) 的决策,要采用区间DP的顺序进行循环。
- 初值:(dp[i][i]=0) ,(p[i][i]=i) 。
Code:
#include<iostream>
#include<cstring>
#define N 3020
using namespace std;
int a[N],p[N][N],dp[N][N],s[N];
int n,m;
int val(int i,int j){
int mid=(i+j)>>1;
return s[i]-s[mid]+(2*mid-i-j)*a[mid]-s[mid-1]+s[j-1];
}
bool Min(int &a,int b){
if(a<=b)return false;
a=b;return true;
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>a[i],s[i]=s[i-1]+a[i];
memset(dp,0x3f,sizeof(dp));
for(int i=0;i<=n;i++)dp[i][i]=0,p[i][i]=i;
for(int len=1;len<=n-m;len++){
for(int i=1,j=1+len;j<=n;i++,j++){
for(int k=p[i][j-1];k<=p[i+1][j];k++)
if(Min(dp[i][j],dp[i-1][k-1]+val(j,k)))p[i][j]=k;
}
}
cout<<dp[m][n];
return 0;
}