BZOJ4385: [POI2015]Wilcze doły

$n leq 2000000$的正数列,有一次机会把一段长度不超过$d$的数变成0,问最长的和不超过$p$的序列。

选的$d$区间一定是答案区间$[L,R]$的最大字段和。可以证明$R$往右时$L$不会往左。反证一下,假如出现了这种情况:

其中蓝色和绿色表示选中的$d$区间(绿不可能在R1左边,否则要么与蓝是最大子段和相悖,要么$L_1$还能再往左延伸到$L_2$)。然后,把这两段切一下:

左边黄色那段两边一样多,右边黄色那段上面要比下面小(去掉蓝绿后),因为蓝色那段是这一小黄段的最大$d$子段和,绿色那段又不到$d$又不是最大,所以剩下那一部分肯定下面的大。既然如此,那$L_1$延伸到$L_2$处都是没有问题的,得证。

知道这个事情之后,就双指针扫下,至于最大子段和,可以搞一个值递减下标递增的单调队列。

 1 //#include<iostream>
 2 #include<cstring>
 3 #include<cstdio>
 4 //#include<math.h>
 5 //#include<set>
 6 //#include<queue>
 7 //#include<bitset>
 8 //#include<vector>
 9 #include<algorithm>
10 #include<stdlib.h>
11 using namespace std;
12 
13 #define LL long long
14 int qread()
15 {
16     char c; int s=0,f=1; while ((c=getchar())<'0' || c>'9') (c=='-') && (f=-1);
17     do s=s*10+c-'0'; while ((c=getchar())>='0' && c<='9'); return s*f;
18 }
19 
20 //Pay attention to '-' , LL and double of qread!!!!
21 
22 int n,D; LL m;
23 #define maxn 2000011
24 int a[maxn]; LL sum[maxn];
25 int que[maxn],head=1,tail=1;
26 
27 int main()
28 {
29     n=qread(); scanf("%lld",&m); D=qread();
30     for (int i=1;i<=n;i++) a[i]=qread(),sum[i]=sum[i-1]+a[i];
31     
32     int L=1,R=D,ans=D; LL tot=sum[D]; que[tail++]=D;
33     for (R++;R<=n;R++)
34     {
35         while (head<tail && sum[R]-sum[R-D]>sum[que[tail-1]]-sum[que[tail-1]-D]) tail--;
36         que[tail++]=R;
37         tot+=a[R];
38         while (tot-(sum[que[head]]-sum[que[head]-D])>m)
39         {
40             tot-=a[L];
41             while (que[head]<L+D) head++;
42             L++;
43         }
44         ans=max(ans,R-L+1);
45     }
46     printf("%d
",ans);
47     return 0;
48 } 
View Code
原文地址:https://www.cnblogs.com/Blue233333/p/9202437.html