描述:
题目给出一个长为 n 序列A,你可以任意调整序列中数字的顺序。使得
∑i=1n−k|A[i]−A[i+k]|
尽可能的小。(序列下标从1开始)
输入:
第一行输入n, k;
(2≤n≤3⋅105,1≤k≤min(5000,n−1))
第二行输入n个数字A[1], A[2],
…
, A[n]。
(−109≤A[i]≤109)
输出:
输出题目要求的最小解。
提示:
对于样例
3 2
1 2 4
的解是
1
其调整后的序列是
1 4 2
思路
题目要求的可以看成是(n-k)个序对的差的绝对值的和,要让题目所取得值尽可能的小,对于 a≤b≤c≤d 有:
|a−b|+|c−d|≤|a−c|+|b−d|
因此应该尽可能的然这些序对大小相邻。
对于第 i 个数字的序对 (i, i+k),第 i+k 个数字的序对是 (i+k, i+2*k) ,以此类推后可以得到一个序列B:
i, i+k, i+2∗k,…, i+m⋅k
对于这个序列的绝对之差的和为
A[i+m⋅k]−A[i]
而对于一个序列A可以分成多少个A这样的序列B呢?对于大小为13,3的数据可以分成这样几个序列:
12345678910111213
我们可以发现,对于长为n的A序列共需要分成k个序列,其中有 n%k 个序列的长度为 ⌊nk⌋+1 而剩下的是k-n%k个长度是 ⌊nk⌋ 。
由于我们之前讨论过这些序列的数字必须相邻所以原先A序列的被分成 连续的k 段,并且其中的数量和长度要满足上述要求。
于是问题变成了把一个长度为n的上升序列分成k段,其中有n%k段长度为 ⌊nk⌋+1 。要求使得这k段的分别首尾相减的和最小。
dp[i][j] 表示前i段有j段是长度为 (⌊nk⌋+1) 的情况下的前 i 段和的最小值
因此我们只要分第 i 段的长度为
(⌊nk⌋+1)
时的结果和长度为
(⌊nk⌋)
的结果。令
LEN=⌊nk⌋
,可以得到转移:
dp[i][j]=min{dp[i−1][j−1]+|A[i⋅LEN+j]−A[(i−1)⋅LEN+(j−1)+1]|dp[i−1][j]+|A[i⋅LEN+j]−A[(i−1)⋅LEN+j+1]|
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 300000 + 5;
const int MAXK = 5000 + 5;
int n, k;
long long a[MAXN];
long long dp[MAXK];
long long _get(int l, int r) {
return a[r] - a[l];
}
int main () {
scanf ("%d%d", &n, &k);
for (int i=1; i<=n; i++) {
scanf ("%I64d", &a[i]);
}
sort(a+1, a+1+n);
int LEN = n / k;
int MAXJ = n % k;
dp[0] = _get(1, 1 * LEN);
dp[1] = _get(1, 1 * LEN + 1);
for (int i=2; i<=k; i++) {
if (MAXJ >= i) {
dp[i] = dp[i-1] + _get((i-1)*LEN+(i-1)+1, i*LEN+i);
}
for (int j=min(MAXJ, i); j>=1; j--) {
if (j == i) continue;
dp[j] = min(dp[j-1] + _get((i-1)*LEN+(j-1)+1, i*LEN+j) ,
dp[j] + _get((i-1)*LEN+j+1, i*LEN+j));
}
dp[0] += _get((i-1)*LEN+1, i*LEN);
}
printf ("%I64d\n", dp[MAXJ]);
return 0;
}