题目大意:
n个正整数
[1,109]
从中选取k个数作为集合I,剩余n-k个数为集合J
最终价值为两集合间两两数字的距离(差的绝对值)
譬如
n = 4 k = 2
4 3 1 2
I = {2,4} J = {1,3}
value=|1−2|+|3−2|+|1−4|+|3−4|=6
要求找出一种方案,令value最小。输出最小的value
首先对n个点排序。
dp[i][j] 表示分配完前i个数,且I集合中已有j个数的最小value。
为了方便转移,dp中存放的不仅是这i个数分出来的集合的value。还需要存后续的连接。
譬如 当前状态
dp[i][j]
还有u个点未分配,I集合还需要v个点。那么到当前点i时,dp中存放的除了I与J集合的距离,还有当前J集合与后面v个将在I集合中的点的距离,还有当前I集合与后面u-v个将在J集合中的点的距离。
不太好理解,或者可以当做对当前边
|num[i]−num[(i+1)]|
进行分配。
其实当前边的统计次数,仅与状态
[i][j]
有关。
为
j∗(n−i−(k−j))+(i−j)∗(k−j)
已分配到I中的数字个数×J集合中未分配的数字个数+已分配到J中的数字个数×I集合中未分配的数字个数。
不同的只是转移方向,如果第i+1个点分配到I集合,转移为
dp[i][j]−>dp[i][j+1]
否则为
dp[i][j]−>dp[i][j]
代码如下:
#include <iostream>
#include <cmath>
#include <vector>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <queue>
#include <stack>
#include <list>
#include <algorithm>
#include <map>
#include <set>
#define LL long long
#define VI vector<int>
#define Pr pair<int,int>
#define fread(ch) freopen(ch,"r",stdin)
#define fwrite(ch) freopen(ch,"w",stdout)
using namespace std;
const int INF = 0x3f3f3f3f;
const int msz = 10000;
const int mod = 1e9+7;
const double eps = 1e-8;
LL a[3333];
LL dp[3333][3333];
int main()
{
//fread("in.in");
//fread("out.out");
int n,k;
scanf("%d%d",&n,&k);
for(int i = 0; i < n; ++i) scanf("%lld",&a[i]);
sort(a,a+n);
memset(dp,-1,sizeof(dp));
for(int i = 0; i < n; ++i)
{
if(!i)
{
dp[i][0] = 0;
dp[i][1] = 0;
}
else
{
for(int j = max(0,k-(n-i-1)); j <= k; ++j)
{
if(dp[i-1][j] == -1) continue;
dp[i][j] = dp[i-1][j]+(a[i]-a[i-1])*j*(n-i-(k-j));
dp[i][j] += (a[i]-a[i-1])*(i-j)*(k-j);
}
for(int j = max(0,k-(n-i)); j < k; ++j)
{
if(dp[i-1][j] != -1)
{
if(dp[i][j+1] == -1) dp[i][j+1] = dp[i-1][j]+(a[i]-a[i-1])*(j*(n-i-(k-j))+(i-j)*(k-j));
else dp[i][j+1] = min(dp[i][j+1],dp[i-1][j]+(a[i]-a[i-1])*(j*(n-i-(k-j))+(i-j)*(k-j)));
}
}
}
}
printf("%lld\n",dp[n-1][k]);
return 0;
}