題目連結: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 }