POJ 3709 K-Anonymous Sequence (单调队列优化)

题意:给定一个不下降数列,一个K,将数列分成若干段,每段的数字个数不小于K,每段的代价是这段内每个数字减去这段中最小数字之和。求一种分法使得总代价最小?

思路:F[i]表示到i的最小代价。f[i]=min(f[j]+sum[i]-sum[j]-(i-j)*a[j+1]);(i-j>=K)

对于j1,j2,j1<j2且j2更优得

f[j1]+sum[i]-sum[j1]-(i-j1)*a[j1+1]>f[j2]+sum[i]-sum[j2]-(i-j2)*a[j2+1]

得到:

f[j1]-sum[j1]+a[j1+1]*j1)-(f[j2]-sum[j2]+a[j2+1]*j2)>=i*(a[j1+1]-a[j2+1])

可以用单调队列维护.

由于每一层需要K个,所以我们延迟入队的时间.

 1 #include<algorithm>
 2 #include<cstdio>
 3 #include<cmath>
 4 #include<cstring>
 5 #include<iostream>
 6 #define ll long long
 7 long long f[1000005],sum[1000005],a[1000005];
 8 int c[1000005];
 9 int n,K,T;
10 ll G(int j,int k){
11     return f[j]-sum[j]+j*a[j+1]-(f[k]-sum[k]+k*a[k+1]);
12 }
13 ll S(int j,int k){
14     return a[j+1]-a[k+1];
15 }
16 int main(){
17     scanf("%d",&T);
18     while (T--){
19         scanf("%d%d",&n,&K);
20         sum[0]=0;
21         for (int i=1;i<=n;i++) scanf("%I64d",&a[i]);
22         for (int i=1;i<=n;i++) sum[i]=sum[i-1]+a[i],f[i]=0;
23         int h=0,t=0;
24         c[t++]=0;f[0]=0;
25         for (int i=1;i<=n;i++){
26             while (h<t-1&&G(c[h],c[h+1])>=i*S(c[h],c[h+1])) h++;    
27             f[i]=(ll)f[c[h]]+sum[i]-sum[c[h]]-(i-c[h])*a[c[h]+1];        
28             if (i>=2*K-1) c[t++]=i-K+1;
29             for (int j=t-2;j>h;j--){
30              int x=c[j-1],y=c[j],z=c[j+1];
31              if (G(x,y)*S(y,z)>=G(y,z)*S(x,y)) c[j]=c[--t];
32              else break;
33             }
34         }
35         printf("%I64d\n",f[n]);
36     }
37 }

 

转载于:https://www.cnblogs.com/qzqzgfy/p/5553607.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值