[JZOJ5936]【NOIP2018模擬10.29】逛公園
Description
策策同學特別喜歡逛公園,公園可以看做有n個景點的序列,每個景點會給策策帶來di 的愉悅度,策策初始有x0 的愉悅度,然而愉悅度也是有上限的,他在每個景點的愉悅度上限為Li ,策策想要從 l 到 r這一段景點中選擇一段景點參觀(從這一段的左端點逛到這一段的右端點),策策想知道他最終的愉悅度的最大值是多少,你能幫幫他嗎?(區間可以為空,也就是說答案最小為x0 )
n,di,q<=40000,Li<=1000000
Solution
一開始還看錯題了…
我們設
表示從l開始走,走到r,初始值為x的最終愉悅度。
那麼題目就是求
顯然
對於x是單調的,即當
再設
,即從第一個開始就被卡住上限。
那麼有結論,
相當於是分中途是否有卡住上限來討論。
容易讓人疑惑的是,如果第一個沒被卡住,後面被卡住了,這種情況算不到。
其實不會,第一個沒被卡住,後面都會被卡住,那麼如果第一個被卡住,後面肯定更大,更會被卡住,因此計算結果是一樣的。
有了這個結論,我們考慮分塊。
對於每一個詢問,兩邊的散塊顯然可以暴力計算,考慮中間的整塊。
對於第j塊,設它的左右邊界分別為
考慮計算左右端點都在第j塊中的子區間貢獻。
顯然,對於兩個子區間 ,若 且 ,那 就沒用了
我們將這n個子區間(
)拉出來按照S排序,類似單調棧維護,這樣G就是遞減的
那麼要求
的最大值,直接二分G這條線和S這條線的交點(S第一個大於G的位置)就行了。
考慮跨塊的情況
假設我們已經知道了第j-1塊能向後貢獻的最大值為C(即第j塊初始開頭最大是多少)
我們現在需要知道的是,結尾在第j塊中的最大值,以及第j塊能貢獻給下一塊的最大值C是多少
那麼我們可以維護一個字首單調棧和字尾單調棧,類似上面的,對於字首單調棧,我們想找到 的最大值,同樣二分。
對於對下一塊的貢獻,要麼是以C為初始走過整一塊,要麼是從這塊中某個位置開始以x為初始,要麼直接以x為初始。第一第三種情況直接計算,第二種情況同樣在字尾單調棧中二分,最後三者取最大值就是對下一塊的最大貢獻。
時間複雜度
如果改用桶排,則可以優化到
根據平衡規劃的思想,如果把塊大小開到
,那麼複雜度可以到
沒寫不知道。。。
反正直接第一種也跑的賊快
Code
#include <cstdio>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;++i)
#define fod(i,a,b) for(int i=a;i>=b;--i)
#define N 40005
#define M 215
using namespace std;
int fr[N],q,n,a[N],n1,lim[N],sum[N],g[M][M][M],le[M],R,lp[M],ls[M];
struct node
{
int x,y;
friend bool operator <(node x,node y)
{
return (x.y<y.y)||(x.y==y.y&&x.x<y.x);
}
}d[M*M],st[M][M*M],pre[M][M],sub[M][M];
int get(int v,node *a,int len)
{
int l=1,r=len;
while(l+1<r)
{
int mid=(l+r)>>1;
if(a[mid].y+v>a[mid].x) r=mid;
else l=mid;
}
int ans=min(a[r].y+v,a[r].x);
if(r>1) ans=max(ans,min(a[r-1].y+v,a[r-1].x));
if(r>2) ans=max(ans,min(a[r-2].y+v,a[r-2].x));
return ans;
}
int main()
{
cin>>n>>q;
fo(i,1,n) scanf("%d",&a[i]),sum[i]=sum[i-1]+a[i]