题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3045
题目大意:有n个数,可以把n个数分成若干组,每组不得小于m个数,每组的价值=除了该组最小值以外每个值-最小值之和,求使得所有组的价值之和的最小值。
解题思路:将n个数按从小到大排序,处理前i为前缀和为sum[i],则可得出状态转移方程:dp[i]=min{dp[j]+sum[i]-sum[j+1]-a[j+1]*(i-j-1)}(0<=j<i-m+1),再用斜率DP优化即可。
注意:一定要判断j是否大于等于m,因为至少m才能算一组奶牛,不然会出错。
代码:
1 #include<iostream> 2 #include<algorithm> 3 #include<cstring> 4 #include<cstdio> 5 using namespace std; 6 typedef long long LL; 7 const int N=5e5+5; 8 9 int head,tail; 10 LL sum[N],dp[N],a[N],q[N];; 11 12 LL getUP(int k,int j){ 13 return dp[j]+a[j+1]*(j+1)-sum[j+1]-dp[k]-a[k+1]*(k+1)+sum[k+1]; 14 } 15 16 LL getDOWN(int k,int j){ 17 return a[j+1]-a[k+1]; 18 } 19 20 //dp[i]=min{dp[j]+sum[i]-sum[j+1]-a[j+1]*(i-j-1)} 21 LL getDP(int i,int j){ 22 return dp[j]+sum[i]-sum[j+1]-a[j+1]*(i-j-1); 23 } 24 25 int main(){ 26 int n,m; 27 while(~scanf("%d %d",&n,&m)){ 28 memset(dp,0x3f3f3f3f,sizeof(dp)); 29 for(int i=1;i<=n;i++){ 30 scanf("%lld",&a[i]); 31 } 32 sort(a+1,a+1+n); 33 for(int i=1;i<=n;i++){ 34 sum[i]=sum[i-1]+a[i]; 35 } 36 dp[0]=0; 37 head=tail=0; 38 q[tail++]=0; 39 for(int i=1;i<=n;i++){ 40 while(head+1<tail&&getUP(q[head],q[head+1])<=i*getDOWN(q[head],q[head+1])){ 41 head++; 42 } 43 dp[i]=getDP(i,q[head]); 44 int j=i-m+1; 45 //注意z这个判断,因为状态转移,也就是分组,至少要保证第一组有m头牛。 46 if(j<m) 47 continue; 48 while(head+1<tail&&getUP(q[tail-1],j)*getDOWN(q[tail-2],q[tail-1])<=getUP(q[tail-2],q[tail-1])*getDOWN(q[tail-1],j)){ 49 tail--; 50 } 51 q[tail++]=j; 52 } 53 printf("%lld ",dp[n]); 54 } 55 56 return 0; 57 }