题目链接:http://codeforces.com/problemset/problem/571/B
题意:
给你一个长度为n的数列a[i]。
现在你可以随意改变数字的位置,问你 ∑| a[i] - a[i+k] | 的最小值(1 <= i <= n-k)。
题解:
将a[i]拆成若干个子序列s[j],子序列中相邻两数在a[i]中的距离为k。
此时原式 = ∑(子序列s[j]内部之差的和)
显然,要想使子序列s[j]内部之差的和尽可能小,子序列s[j]内部一定为升序。
显然,要想使 ∑(子序列s[j]内部之差的和)尽可能小,所有子序列s[j]一定是由a[i]升序排序后分割而来。
可以发现,拆出的子序列中:
有 n2 = n%k 个子序列长度为 l1 = n/k+1
有 n1 = k-n%k 个子序列长度为 l2 = n/k
此时:
原式 = ∑ (s[2]-s[1]+s[3]-s[2]+s[4]-s[3]...)
前后相消之后就是:
原式 = ∑ (s[i][end] - s[i][1])
此时题目就变成了:
先将a[i]排序,然后将a[i]分割成n1个长为l1的子串,以及n2个长为l2的子串。
让你使得 ∑ (s[i][end] - s[i][1])最小。
表示状态:
dp[i][j]
表示从头开始分割,已经分割出了i个长为l1的子串,以及j个长为l2的子串。
找出答案:
ans = dp[n1][n2]
如何转移:
if(i) dp[i][j] = min(dp[i][j], dp[i-1][j]+a[start1]-a[end1])
if(j) dp[i][j] = min(dp[i][j], dp[i][j-1]+a[start2]-a[end2])
start1/2, end1/2分别是新分割出的子串的首位与末尾。
边界条件:
dp[0][0] = 0
others = INF
AC Code:
1 #include <iostream> 2 #include <stdio.h> 3 #include <string.h> 4 #include <algorithm> 5 #define MAX_N 300005 6 #define MAX_S 5005 7 8 using namespace std; 9 10 int n,k; 11 int a[MAX_N]; 12 long long dp[MAX_S][MAX_S]; 13 14 int main() 15 { 16 cin>>n>>k; 17 for(int i=1;i<=n;i++) cin>>a[i]; 18 sort(a+1,a+n+1); 19 int n1=n%k,n2=k-n%k; 20 int l1=n/k+1,l2=n/k; 21 memset(dp,0x3f,sizeof(dp)); 22 dp[0][0]=0; 23 for(int i=0;i<=n1;i++) 24 { 25 for(int j=0;j<=n2;j++) 26 { 27 if(i) dp[i][j]=min(dp[i][j],dp[i-1][j]+a[i*l1+j*l2]-a[(i-1)*l1+j*l2+1]); 28 if(j) dp[i][j]=min(dp[i][j],dp[i][j-1]+a[i*l1+j*l2]-a[i*l1+(j-1)*l2+1]); 29 } 30 } 31 cout<<dp[n1][n2]<<endl; 32 }