dp专辑 C - K-Anonymous Sequence [ 斜率优化]

这是我做的第二道斜率优化题了,开始题目米看懂,就直接百度了,才明白题意~英语不行呀大哭~


题意:

将一个升序的,有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,BA,删除A,否则不需维护.

 

队尾维护:

    假设A,B,C(A<B<C)是队尾元素

a.X(B,A)<=i,X(C,B)<=i,CB,BA

b.X(B,A)<=i,X(C,B)>i,BC,BA,B为极大值

c.X(B,A)>i,AB

 

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;
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值