令 T T T 表示一个整数的集合, M I N , M A X MIN,MAX MIN,MAX 分别是这个集合中最小和最大的数,定义这个集合的花费为 ( M A X − M I N ) 2 (MAX-MIN)^2 (MAX−MIN)2。
先给定一个整数集合 S S S,希望找到 M M M 个 S S S 的子集 S 1 , S 2 , ⋯ S M S_1,S_2,\cdots S_M S1,S2,⋯SM,使得 S 1 ∪ S 2 ∪ ⋯ ∪ S M = S S_1\cup S_2\cup\cdots\cup S_M=S S1∪S2∪⋯∪SM=S,并且整体的花费最小。
要使花费小的话肯定要保证集合中最大值和最小值的差较小。首先对元素进行排序,然后用 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示前 i i i 个数分成 j j j 个集合的最小花费,递推方程为:
d p [ i ] [ j ] = min { d p [ k ] [ j − 1 ] + ( a [ i ] − a [ k + 1 ] ) 2 } dp[i][j]=\min\{dp[k][j-1]+(a[i]-a[k+1])^2\} dp[i][j]=min{dp[k][j−1]+(a[i]−a[k+1])2}
还需要考虑的是两个子集之间的元素有可能重复,但是可以证明,子集尽量不相交时可使花费最小:假如子集够多,那么每个元素单独成一个子集,这样的花费是最小的。
再设 k 1 < k 2 k_1<k_2 k1<k2 且 k 2 k_2 k2 优于 k 1 k_1 k1,则:
d p [ k 2 ] [ j − 1 ] + ( a [ i ] − a [ k 2 + 1 ] ) 2 < d p [ k 1 ] [ j − 1 ] + ( a [ i ] − a [ k 1 + 1 ] ) 2 d p [ k 2 ] [ j − 1 ] + a 2 [ k 2 + 1 ] − ( d p [ k 1 ] [ j − 1 ] + a 2 [ k 1 + 1 ] ) < 2 a [ i ] ( a [ k 2 + 1 ] − a [ k 1 + 1 ] ) y k 2 − y k 1 x k 2 − x k 1 < 2 a [ i ] \begin{aligned} dp[k_2][j-1]+(a[i]-a[k_2+1])^2&<dp[k_1][j-1]+(a[i]-a[k_1+1])^2\\ dp[k_2][j-1]+a^2[k_2+1]-(dp[k_1][j-1]+a^2[k_1+1])&<2a[i](a[k_2+1]-a[k_1+1])\\ \frac{y_{k_2}-y_{k_1}}{x_{k_2}-x_{k_1}}&<2a[i] \end{aligned} dp[k2][j−1]+(a[i]−a[k2+1])2dp[k2][j−1]+a2[k2+1]−(dp[k1][j−1]+a2[k1+1])xk2−xk1yk2−yk1<dp[k1][j−1]+(a[i]−a[k1+1])2<2a[i](a[k2+1]−a[k1+1])<2a[i]
其中 y k = d p [ k ] [ j − 1 ] + a 2 [ k + 1 ] , x k = a [ k + 1 ] y_k=dp[k][j-1]+a^2[k+1],x_k=a[k+1] yk=dp[k][j−1]+a2[k+1],xk=a[k+1], a [ i ] a[i] a[i] 为排序后第 i i i 个元素的值。
代码如下:
#include<iostream>
#include<cstdio>
#include<algorithm>
//#define WINE
#define MAXN 10010
using namespace std;
int T,iCase,n,m,a[MAXN],h,t,q[MAXN],dp[MAXN][MAXN];
int up(int k2,int k1,int j){
return dp[k2][j-1]+a[k2+1]*a[k2+1]-(dp[k1][j-1]+a[k1+1]*a[k1+1]);
}
int down(int k2,int k1){
return a[k2+1]-a[k1+1];
}
int getDP(int k,int i,int j){
return dp[k][j-1]+(a[i]-a[k+1])*(a[i]-a[k+1]);
}
int main(){
#ifdef WINE
freopen("data.in","r",stdin);
#endif
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
sort(a+1,a+1+n);
for(int i=1;i<=n;i++)dp[i][1]=(a[i]-a[1])*(a[i]-a[1]);
for(int j=2;j<=m;j++){
h=t=0;q[t++]=j-1;
for(int i=j;i<=n;i++){
while(h+1<t&&up(q[h+1],q[h],j)<2*a[i]*down(q[h+1],q[h]))
h++;
dp[i][j]=getDP(q[h],i,j);
while(h+1<t&&up(i,q[t-1],j)*down(q[t-1],q[t-2])<=up(q[t-1],q[t-2],j)*down(i,q[t-1]))
t--;
q[t++]=i;
}
}
printf("Case %d: %d\n",++iCase,dp[n][m]);
}
return 0;
}