这是我做的第二道斜率优化题了,开始题目米看懂,就直接百度了,才明白题意~英语不行呀~
题意:
将一个升序的,有N个元素的序列,分组。每组的元素不少于K个,计算出组内各元素与最小元素的之差的和,将每组的这个值加起来,其和要最小。
分析:
DP方程:dp[i]=MIN(dp[j]+sum[i]-sum[j]-(i-j)*arr[j+1]); j<i-k+1
其中:dp[i]:1到i的最小和,sum[i]: 1到i的序列和;sum[i]-sum[j]:序列j到i的和,(i-j)*arr[j+1]:个数*最小值 结合在一起就是 组内各元素与最小元素的之差的和
开始斜率优化:
1、求斜率方程
化简得:
令G(k,j)= dp[j]-dp[k]-sum[j]+sum[k]+j*arr[j+1]-k*arr[k+1]
则上式化为G(k,j)>=i*S(k,j)
即G(k,j)/S(k,j)<=i 记住变号,因为S(k,j)<0
令X(k,j)= G(k,j)/S(k,j)
所以斜率方程:X(k,j)<=i
2、规定队列的维护规则
队首维护:
队尾维护:
a.若X(B,A)<=i,且X(C,B)<=i,则C比B好,B比A好
b.若X(B,A)<=i,且X(C,B)>i,则B比C好,B比A好,B为极大值
c.若X(B,A)>i,A比B好
a,c情况直接删掉B,b情况保留.b情况可改为X(B,A)<X(C,B)
3、考虑每组不少于K个元素的限制
要解决这个限制,只需延迟加入的时机即可。
若延迟K-1个回合加入,有可能使前一组的个数少于K个。
若延迟2*k-1个回合加入,则不会出现这情况。但此时加入的数应是i-k+1(假设是第I回合)
//AC CODE:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef __int64 LL;
const int N=500000+10;
int q[N];//队列
LL dp[N];
LL arr[N];//输入的数据
LL sum[N];//1~i的和
LL G(int k,int j)
{
return dp[j]-dp[k]-sum[j]+sum[k]+j*arr[j+1]-k*arr[k+1];
}
LL S(int k,int j)
{
return arr[j+1]-arr[k+1];
}
int main()
{
int i,j,t,n,k,x,y,z;
scanf("%d",&t);
while( t-- )
{
scanf("%d%d",&n,&k);
sum[0]=0;
for(i=1; i<=n; ++i)
{
scanf("%I64d",arr+i);
sum[i]=sum[i-1]+arr[i];
}
dp[0]=0;
int head=0,tail=0;
q[tail++]=0;
for(i=1; i<=n; ++i)
{
//处理队首
while( head<tail-1 && G(q[head+1],q[head])>=i*S(q[head+1],q[head]) )
++head;
j=q[head];
dp[i]=dp[j]+sum[i]-sum[j]-(i-j)*arr[j+1];// dp方程
//延迟加入
if( i>=2*k-1 )
q[tail++]=i-k+1;
//处理队尾
for(j=tail-2; j>head; --j)
{
x=q[j-1],y=q[j],z=q[j+1];
//if( (G(z,y)>=i*S(z,y) && G(y,x)>=i*S(y,x)) || (G(x,y)>=i*S(x,y)) )
if( !(G(y,x)*S(z,y)<G(z,y)*S(y,x)) )
q[j]=q[--tail];
else
break;
}
}
printf("%I64d\n",dp[n]);
}
return 0;
}