HDU 3480 Division 斜率DP
令 表示一个整数的集合, 分别是这个集合中最小和最大的数,定义这个集合的花费为 。
先给定一个整数集合 ,希望找到 个 的子集 ,使得 ,并且整体的花费最小。
要使花费小的话肯定要保证集合中最大值和最小值的差较小。首先对元素进行排序,然后用 表示前 个数分成 个集合的最小花费,递推方程为:
还需要考虑的是两个子集之间的元素有可能重复,但是可以证明,子集尽量不相交时可使花费最小:假如子集够多,那么每个元素单独成一个子集,这样的花费是最小的。
再设 且 优于 ,则:
其中 , 为排序后第 个元素的值。
代码如下:
#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;
}