1. 程式人生 > >洛谷3594 [POI2015]WIL-Wilcze doły(單調佇列)

洛谷3594 [POI2015]WIL-Wilcze doły(單調佇列)

題目

給定一個長度為n的序列,你有一次機會選中一段連續的長度不超過d的區間,將裡面所有數字全部修改為0。請找到最長的一段連續區間,使得該區間內所有數字之和不超過p。

特性

選擇一個區間[i,i+d-1],那麼我們選擇的最長區間一定在這個區間附近,也就是連續的。

題解

單調佇列
很明顯是一個O(N)的演算法,那麼時間只夠我們列舉一個右端點。
列舉右端點後,最優的左端點在哪裡呢?很明顯,這個左端點隨著右端點的右移,它也會隨之右移或者原地不動。
我們選的數 = 區間和 - 區間一段長度為d的區間
我們要維護的就是區間內一段長度為d的最大和區間,需要維護一個單調遞減的佇列。
當選的數大於p時,就要縮短區間,同時彈出起點超出last的長度為d的區間。

程式碼

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=2000010;
inline ll read()
{
    ll re=0;char ch=getchar();
    while(ch<'0' || ch>'9') ch=getchar();
    while(ch>='0' && ch<='9') re=re*10+(ch^48),ch=getchar();
    return re;
}

int n=read(),p=read();ll d=read();
ll s[maxn],t[maxn];//t記錄長度為d的一段區間的和
int l,r,q[maxn];

int main()
{
    for(int i=1;i<=n;i++) s[i]=s[i-1]+read();
    for(int i=d;i<=n;i++) t[i]=s[i]-s[i-d];//debug i:1~n-d+1
    l=r=0;q[0]=d;
    int ans=d,last=1;
    for(int i=d+1;i<=n;i++)
    {
        while(l<=r && t[q[r]]<=t[i]) r--;
        q[++r]=i;
//        while(l<=r && q[l]-d+1<last) l++;
        while(s[i]-s[last-1]-t[q[l]]>p)
        {
            last++;
            if(l<=r && q[l]-d+1<last) l++;//debug q[l]-d+1才是開頭 
        }
        ans=max(ans,i-last+1);
    }
    printf("%d\n",ans);
    return 0;
}