NKOJ-3768 數列操作

P3768 數列操作
問題描述

    給出 N 個正整數數列 a[1..N],再給出一個正整數 k,現在可以重複進行如下操作:
        每次選擇一個大於 k 的正整數 a[i],將 a[i]減去 1,選擇 a[i-1]或 a[i+1]中的一個加上 1。
    經過一定次數的操作後,問最大能夠選出多長的一個連續子序列,使得這個子序列的每個數都不小於 k。                                 
    總共給出 M 次詢問,每次詢問給出的 k 不同,你需要分別回答。。

輸入格式

第一行兩個正整數 N 和 M  。               
第二行 N 個正整數,第 i 個正整數表示 a[i] 。    
第三行 M 個正整數,第 i 個正整數表示第 i 次詢問的 k 。   

輸出格式

共一行,輸出 M 個正整數,第 i 個數表示第 i 次詢問的答案。   

輸入輸出樣例
樣例輸入 1

5 6             
1 2 1 1 5   
1   2   3   4   5   6

樣例輸出 1

5 5 2 1 1 0

樣例輸入 2

6 4     
3 2 1 2 5 1
3   4   1   6

樣例輸出 2

2 1 6 0

數據範圍

對於 40%的數據, 1<=N<=100    1<=M<=50    
對於 100%的數據,1<=N<=300000 1<=M<=50a[i] <= 10^9    k <= 10^9

你永遠想不到第一題有多難

題解

先簡化一下題意

題目當中的操作其實可以轉化爲:
對於區間[l,r],若sum[l,r]>=(r-l+1)*k,則該區間滿足要求 操作後每個數字不小於k

然後就是一件很糾結的事情

其實我們算的是 sum[l,r] - (r-l+1)*k 是否大於0
但是對於不同的區間[l,r],減去的(r-l+1)*k是不同的 這就很難處理了

所以我們先做一點簡單的

對於區間[1,n] 求出最長的子區間[l,r] 滿足sum[l,r]>=0
這道題目就簡單多了

①求出前綴和的單調遞減序列
②求出滿足sum[1,r]-sum[1,l]>0的 r-l 最大值

例如 區間元素爲       1 -2 1 1 -4  1 2
那麼前綴和爲          1 -1 0 1 -3 -2 0
單調遞減序列 0(pos=0)   -1     -3

於是在求r-l的最大值時 我們就枚舉pos=7 -> pos=1
求出對於r 最小的單調序列的編號

pos=7時 單調隊列的當前編號爲5,對應的前綴和爲-3
sum[7] - sum[5] = 0(第七個位置的前綴和)-(-3)(第五個位置的前綴和)=3>0
所以可以更新最大長度爲 7-5+1=3

如此往復 即可得到對於7的最長序列爲 7(pos=0的點sum=0->滿足條件)

爲什麼我們只需要考慮單調隊列中的元素呢?

對於單調隊列中的任意兩個連續的下標 l,r 當天討論的點爲 p
如果 sum[p]-sum[l]<0
那麼由於隊列的單調性 sum[p+k]>=sum[p] (p+k<l)
如果sum[p]不滿足要求 
    那麼由於sum[p+k]>=sum[p]所以sum[p+k]也一定不滿足要求
反之 如果sum[p+k]滿足要求 那麼sum[p]也一定能滿足要求

小問題解決了

那麼我們能不能把原來的問題轉化成這個問題呢?

答案是肯定的

我們可以把數列中的每一個數字都減去 k
轉化後 我們求得等價於原來的
sum[l,r] -(r-l+1)k >= (r-l+1)k -(r-l+1)k
[黑色的字體即爲減去的k們]

問題解決

注意

①要用long long(套路真的深)
②對於最長的滿足要求的序列即爲原序列的特殊情況,我們應該把隊列中的第一個元素位置賦值爲0 sum[0]=0 (具體原因自己想想 當然你也可以先WA再想)

附上對拍代碼

#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 n,m,res,k,top;
long long a[300123],sum[300123],sq[300123];

int main()
{
//  freopen("sequence.in","r",stdin);
//  freopen("sequence.out","w",stdout);
    n=input();m=input();
    for(long long i=1;i<=n;i++)a[i]=input();
    for(long long t=1;t<=m;t++)
    {
        k=input();res=0;top=1;
        for(long long i=1;i<=n;i++)
        {
            sum[i]=sum[i-1]+a[i]-k;
            if(sum[i]<sum[sq[top]])sq[++top]=i;
        }
        for(long long i=n;i;i--)
        {
            while(top&&sum[sq[top]]<=sum[i])top--;
            res=max(res,i-sq[top+1]);
        }
        printf("%d ",res);
    }
}
發佈了79 篇原創文章 · 獲贊 15 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章