Codeforces 571B Minimization:dp + 貪心【前後相消】

Leohh發表於2018-01-06

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

 

相關文章