题意:
给定一个N≤3×105的序列,给定一个1≤K≤min(5000,n−1)
通过重排原序列,求最小的∑n−ki=1|Ai−Ai+k|
分析:
观察这个式子,里面其实就是|Aj−Ai|,j≡i mod k
那么我们把原序列按照模k的余数分类,总共有k个子序列,0∼k−1
发现其中有n%k个子序列的长度为n/k+1,n−n%k个长度为n/k
假设其实一个序列是b1,b2,...,bm这样排列值最优的
它的贡献就是Ei=|b2−b1|+|b3−b2|+|b4−b3|+⋯+|bm−bm−1|
让这样的贡献最小,显然我们应该让这个序列的相邻数的差距最近
那么这样的序列一定是原序列有序排序后,其中连续的一部分
这样之后b1≤b2≤⋯≤bm
显然Ei=b2−b1+b3−b2+b4−b3+⋯+bm−bm−1=bm−b1
那么原问题就转化为有序序列中选取n%k个长连续子序列,n−n%k个短连续子序列的最优解
dp[large][small]:=选取large个长连续子序列,small个短连续子序列的最优解
转移就枚举选取的这个是长还是短就可以了
时间复杂度为O(nlogn+k2)
注意细节。
代码:
//
// Created by TaoSama on 2016-02-04
// Copyright (c) 2016 TaoSama. All rights reserved.
//
#pragma comment(linker, "/STACK:1024000000,1024000000")
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <string>
#include <set>
#include <vector>
using namespace std;
#define pr(x) cout << #x << " = " << x << " "
#define prln(x) cout << #x << " = " << x << endl
const int N = 3e5 + 10, INF = 0x3f3f3f3f, MOD = 1e9 + 7;
typedef long long LL;
int n, k, a[N];
LL f[5005][5005];
int main() {
#ifdef LOCAL
freopen("C:\\Users\\TaoSama\\Desktop\\in.txt", "r", stdin);
// freopen("C:\\Users\\TaoSama\\Desktop\\out.txt","w",stdout);
#endif
ios_base::sync_with_stdio(0);
while(scanf("%d%d", &n, &k) == 2) {
for(int i = 1; i <= n; ++i) scanf("%d", a + i);
sort(a + 1, a + 1 + n);
int len = n / k, large = n % k, small = k - large;
memset(f, 0x3f, sizeof f);
f[0][0] = 0;
for(int i = 0; i <= large; ++i) {
for(int j = 0; j <= small; ++j) {
int st = i * (len + 1) + j * len + 1;
if(i < large) {
int largeCost = a[st + len] - a[st];
f[i + 1][j] = min(f[i + 1][j], f[i][j] + largeCost);
}
if(j < small) {
int smallCost = a[st + len - 1] - a[st];
f[i][j + 1] = min(f[i][j + 1], f[i][j] + smallCost);
}
}
}
printf("%I64d\n", f[large][small]);
}
return 0;
}