$dp$,二分,预处理。
设$dp[i][j]$表示前$i$个村庄,放了$j$个邮局,且$i$位置放了一个邮局的最小代价。答案产生于:$min$$($$dp[x][m]+x$位置之后的代价$)$。然后就可以推了,两个邮局之间的产生的代价可以二分预处理一下。
#pragma comment(linker, "/STACK:1024000000,1024000000") #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #include<vector> #include<map> #include<set> #include<queue> #include<stack> #include<ctime> #include<iostream> using namespace std; typedef long long LL; const double pi=acos(-1.0),eps=1e-10; void File() { freopen("D:\in.txt","r",stdin); freopen("D:\out.txt","w",stdout); } template <class T> inline void read(T &x) { char c = getchar(); x = 0; while(!isdigit(c)) c = getchar(); while(isdigit(c)) { x = x * 10 + c - '0'; c = getchar(); } } int n,m; long long x[400],dp[400][40]; int P[400][400]; long long s1[400],s2[400]; long long ABS(long long a) { if(a>0) return a; return -a; } bool check(int a,int b,int c) { if(ABS(x[a]-x[c])>=ABS(x[b]-x[c])) return 1; return 0; } int main() { while(~scanf("%d%d",&n,&m)) { for(int i=1;i<=n;i++) scanf("%lld",&x[i]); sort(x+1,x+1+n); memset(s1,0,sizeof s1); memset(s2,0,sizeof s2); for(int i=1;i<=n;i++) s1[i]=s1[i-1]+x[i]-x[1]; for(int i=n;i>=1;i--) s2[i]=s2[i+1]+x[n]-x[i]; for(int i=1;i<=n;i++) { for(int j=i+1;j<=n;j++) { int L=i+1,R=j; while(L<=R) { int mid=(L+R)/2; if(check(i,j,mid)) R=mid-1,P[i][j]=mid; else L=mid+1; } } } for(int i=1;i<=n;i++) dp[i][1]=dp[i-1][1]+(i-1)*(x[i]-x[i-1]); for(int j=2;j<=m;j++) { for(int i=j;i<=n;i++) { dp[i][j]=999999999999999; for(int k=j-1;k<i;k++) { int pos=P[k][i]; dp[i][j] = min(dp[i][j], dp[k][j-1] +(s1[pos-1]-s1[k])-(pos-k-1)*(x[k]-x[1]) +(s2[pos]-s2[i])-(i-pos)*(x[n]-x[i]) ); } } } long long ans=999999999999999; for(int i=m;i<=n;i++) ans=min(ans,dp[i][m]+s1[n]-s1[i]-(n-i)*(x[i]-x[1])); printf("%lld ",ans); } return 0; }