NKOJ-3775 數列操作

P3775數列操作
時間限制 : - MS 空間限制 : 165536 KB
評測說明 : 1000ms
問題描述

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

輸入格式

第一行包含三個整數n,p,d(1<=d<=n<=300000,0<=p<=10^16)。
第二行包含n個正整數,依次表示序列中每個數w[i](1<=w[i]<=10^9)。

輸出格式

包含一行一個正整數,即修改後能找到的最長的符合條件的區間的長度。

樣例輸入

9 7 2
3 4 1 9 4 1 7 1 3

樣例輸出

5

提示

將第4個和第5個數修改爲0,然後可以選出區間[2,6],總和爲4+1+0+0+1=6。

來源 poi 2015 Wilcze doły

感覺自己的單調隊列學的真的菜…

題解

簡單的分析

比較容易想到的一個處理是 對每個點都求出以它爲起點(或者是終點)的 長度不大於d的區間和
按貪心來講 除非是無法使長度達到d 否則是一定要刪掉長度爲d的區間的

那麼我們的問題就在於 如何能夠在一段區間內快速尋找到長度爲d的和最大的子區間

暫時拋去這些不看 我們先看一些基礎操作

基礎操作

首先肯定是預處理出前綴和sum
那麼對於每個點p爲終點的長度不大於d的子區間

在1<=p<=d時 子區間和爲 sum[p]
而當d+1<=p<=n時 子區間和則爲 sum[p]-sum[p-d]

於是對於一段區間[l,r] 假如最大長度爲d的子區間和爲 delta
那麼我們需要判斷的即是 sum[r]-sum[l-1]-delta 是否 <=p

對於這道題目來講 時間複雜度O(n^2)是不允許的
我們所能夠接受的最大時間複雜度爲O(nlog n)(原題的元素個數爲200 0000)

但是就目前來看 我們必須討論以每個點爲終點的情況才能夠得到結果
這就要求我們的查詢的次數一定要小於logn
所以其實你也可以用線段樹什麼的(理論上可行)

具體的做法是
把每個點爲起點的d長度區間之和作爲每個點的權值建樹
然後查詢一段區間的最大子區間和就是常規的線段樹操作
(沒有嘗試過 只是一個想法而已)
可以用二分法求左端點l 然後每次查詢線段樹[l,r](r爲當前討論的終點)中的最大值
通過上面的判斷條件即可判斷能否成立
大概的時間複雜度是(log n*log n)【二分法*線段樹】

但是我們有一個更簡單的工具 單調隊列

單調隊列部分

單調遞減隊列 存入長度爲d的區間的值
對於每個點p爲終點的討論 我們都在隊列中加入 sum[p]-sum[p-d] 使得討論一定有解(標記位置應爲p-d+1)

具體操作(請自行配合樣例畫圖進行理解)
假設我們現在已經得到可以成立的區間[l,r](即[l-1,r]不能使條件成立)
那麼在我們討論以r爲右端點的成立的區間時 [l-1,r+1]一定是不能成立的
([l-1,r]都不能成立了 [l-1,r+1]還要多一個數字 就更加不可能成立了)

注意此時的右端點全部是 r+1

所以我們在討論r+1爲右端點時 左端點直接從l開始枚舉
而且對於單調隊列中的元素 左端點也一定要>=r+1

由於單調隊列爲單調遞減隊列 所以第一個元素一定是當前區間中 長度爲d的子區間的和的最大值
所以這樣就能夠輕易地得到上面我們想要的結果
並且由於左端點一定在區間之中 且左端點一定<=(r+1)-d+1 (因爲我們在單調隊列中加入的最後一個元素的左端點爲 (r+1)-d+1)
所以刪去的區間並不會越界

所以此時我們只需要判斷 sum[r+1]-sum[l-1]-隊列第一個元素 是否<=p 即可判定成立與否

若成立 那麼就可以直接更新結果 且以r+1爲右端點的討論終止(思考原因)
若不成立 那麼l+1 同時對當前單調隊列的元素的左端點進行判定(左端點>=l+1)
        然後重複上述過程

這個樣子 其實以左端點也只是把每一個點都討論了一次
看似時將每一個點作爲右端點的情況都討論了很久
但是其實討論的總的時間複雜度不過是O(n)

求解結束

注意

要用long long !!(這真的是終點)

附上對拍代碼

#include <iostream>
#include <cstdio>
using namespace std;

inline long long input()
{
    char c=getchar();long long o;
    while(c>57||c<48)c=getchar();
    for(o=0;c>47&&c<58;c=getchar())o=(o<<1)+(o<<3)+c-48;
    return o;
}

long long que[300123],pos[300123],sum[300123],add;
int res,nl=1,head=1,tail=0,l;

int main()
{
    long long n=input(),p=input(),d=input();
    res=d;
    for(int i=1;i<=n;i++)sum[i]=sum[i-1]+input();
    for(int i=d+1;i<=n;i++)
    {
        add=sum[i]-sum[i-d];
        while(head<=tail&&add>=que[tail])tail--;
        que[++tail]=add;pos[tail]=i-d+1;
        while(sum[i]-sum[nl-1]>p+que[head])
        {
            nl++;
            while(pos[head]<nl)head++;
            res=max(res,i-nl+1);
    }
    printf("%d",res);
}
發佈了79 篇原創文章 · 獲贊 15 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章