题意:
给你一个有n个数的集合S,现在让你选出m个子集合,使这m个子集合并起来为S,并且每个集合的(max-min)2 之和要最小。
题解:
运用贪心的思想,肯定首先将全部的数排好序,然后设dp[i][j]表示前j个数分为i个集合的最优解。
则有dp[i][j]=min{dp[i-1][k]+(a[j]-a[k+1])2}(0<k<j)。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define mp make_pair
const int maxn = 10001;
ll dp[maxn][maxn/2];
typedef pair <ll, ll> Line;
#define k first
#define b second
struct ConvexHull
{
Line stk[maxn];
int l, r;
void init(){ l = r = 0; }
//sz r - l [l,r)
bool cover(Line &a, Line &b, Line &c)
{
// line a and b cover line c
return (a.k - b.k) * (b.b - c.b) >= (b.b - a.b) * (c.k - b.k);
// >= 下凸包 求最小值
// <= 上凸包 求最大值
}
void add(Line p)
{
while(r - l > 1 && cover(p,stk[r-2],stk[r-1]))r--;
stk[r++] = p;
}
ll calc(ll x, Line l){ return l.k * x + l.b; }
ll ask(ll x)
{
while(r - l > 1 && calc(x,stk[l+1]) <= calc(x,stk[l]))l++;
return calc(x,stk[l]);
}
}hull;
#undef k
#undef b
ll sq(ll x){ return x*x; }
ll v[maxn];
int n,m;
int T;
int main()
{
scanf("%d",&T);
for(int cas=1;cas<=T;cas++)
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%lld",&v[i]);
}
sort(v+1,v+n+1);
dp[0][0]=0;
for(int i=1;i<=n;i++)
dp[i][1]=sq(v[i]-v[1]);
for(int _=2;_<=m;_++)
{
hull.init();
hull.add(mp(-2*v[1],sq(v[1])));
for(int i=1;i<=n;i++)
{
dp[i][_] = hull.ask(v[i]) + sq(v[i]);
hull.add( mp( -2*v[i+1],sq(v[i+1]) + dp[i][_-1] ) );
}
}
printf("Case %d: %lld\n",cas,dp[n][m]);
}
}