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);
}
}