这是我做的第二道斜率优化题了,开始题目米看懂,就直接百度了,才明白题意~英语不行呀~
题意:
将一个升序的,有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、求斜率方程
假设J<K,且在K点的决策比J好,则有
dp[j]+sum[i]-sum[j]-(i-j)*arr[j+1]>= dp[k]+sum[i]-sum[k]-(i-k)*arr[k+1]
化简得:
dp[j]-dp[k]-sum[j]+sum[k]+j*arr[j+1]-k*arr[k+1]>=i* (arr[j+1]-arr[k+1])
令G(k,j)= dp[j]-dp[k]-sum[j]+sum[k]+j*arr[j+1]-k*arr[k+1]
S(k,j)= arr[j+1]-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,B(A<B)是队首元素,若X(B,A)<=i,则B比A好,删除A,否则不需维护.
队尾维护:
假设A,B,C(A<B<C)是队尾元素
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; }