有p个邮局,n个村庄,邮局只能建在村庄里,
求令所有寄信距离之和最短的值。
数据比较弱,O(n^3)过了。
dp[i][j]表示前i个村庄需要j个邮局,
其必为某个前k个村庄用j-1个邮局,k+1到i用1个邮局的最小值。
即可实现转移,对于某段区间建一个的最小值,邮局一定建在中位数即可。
如果有两个中位数,显然两个地点的值是相等的。
从左中位数a转移到右中位数b,sum-左一半点*dis(a,b)+右一半点*dis(a,b)=sum,没变
求dis(i,j)时用的前缀和,貌似还有一个O(n²)的转移做法。①
感觉dp的下标略坑,总是搞不明白,
而且dp的预处理也不知道第一行处理为什么好。
据说有O(n²)的四边形优化,待补。②
k的内层循环据说也可以优化一下,不是0到i-1。③
一题耽误6-7hGG。
#include <iostream> #include <algorithm> #include <cstring> #include <cstdio> #include <cmath> #include <set> #include <map> #include <vector> #include <stack> #include <queue> #include <functional> const int INF=0x3f3f3f3f; const int maxn=300+10; const int mod=1e9+7; const int MOD=998244353; const double eps=1e-7; typedef long long ll; #define vi vector<int> #define si set<int> #define pii pair<int,int> #define pi acos(-1.0) #define pb push_back #define mp make_pair #define lowbit(x) (x&(-x)) #define sci(x) scanf("%d",&(x)) #define scll(x) scanf("%lld",&(x)) #define sclf(x) scanf("%lf",&(x)) #define pri(x) printf("%d",(x)) #define rep(i,j,k) for(int i=j;i<=k;++i) #define per(i,j,k) for(int i=j;i>=k;--i) #define mem(a,b) memset(a,b,sizeof(a)) using namespace std; //dp[i][j]代表前i个村庄 需要j个邮局的最小花费 // int n,p,a[maxn],sum[maxn],dis[maxn][maxn],dp[maxn][maxn]; int main() { while(~scanf("%d%d",&n,&p)) { rep(i,1,n)sci(a[i]); sort(a+1,a+n+1); sum[0]=0; rep(i,1,n)sum[i]=sum[i-1]+a[i]; rep(i,1,n) { dis[i][i]=0; rep(j,i+1,n) { int mid=(i+j)/2; //画图可证明 坐标零点挪动为a[mid]的前缀和 可理解成每个相加 最后提出a[mid] //sum[j]-sum[mid]为[mid+1,j]的求和,sum[mid-1]-sum[i-1]为[i,mid-1]的求和 dis[i][j]=(sum[j]-sum[mid])-(j-mid)*a[mid]+(mid-i)*a[mid]-(sum[mid-1]-sum[i-1]); } } mem(dp,INF); rep(i,1,n)dp[i][1]=dis[1][i]; rep(j,2,p) { per(i,n,1) { rep(k,1,i-1) { //前k个用j-1个邮局 k+1到i用1个邮局 dp[i][j]=min(dp[k][j-1]+dis[k+1][i],dp[i][j]); } } } printf("%d\n",dp[n][p]);//前n个用p个邮局 } return 0; }