1. 程式人生 > >[JZOJ5936]【NOIP2018模擬10.29】逛公園

[JZOJ5936]【NOIP2018模擬10.29】逛公園

Description

策策同學特別喜歡逛公園,公園可以看做有n個景點的序列,每個景點會給策策帶來di 的愉悅度,策策初始有x0 的愉悅度,然而愉悅度也是有上限的,他在每個景點的愉悅度上限為Li ,策策想要從 l 到 r這一段景點中選擇一段景點參觀(從這一段的左端點逛到這一段的右端點),策策想知道他最終的愉悅度的最大值是多少,你能幫幫他嗎?(區間可以為空,也就是說答案最小為x0 )
n,di,q<=40000,Li<=1000000

Solution

一開始還看錯題了…

我們設 F

( l , r , x ) F(l,r,x) 表示從l開始走,走到r,初始值為x的最終愉悅度。
那麼題目就是求 max
( F ( u , v , x )
)
, [ u , v ] [ l , r ] \max\left(F(u,v,x)\right),[u,v]\in[l,r]

顯然 F ( l , r , x ) F(l,r,x) 對於x是單調的,即當 x 1 x 2 , F ( l , r , x 1 ) F ( l , r , x 2 ) x_1\geq x_2,F(l,r,x_1)\geq F(l,r,x_2)
再設
G ( l , r ) = F ( l , r , I N F ) G(l,r)=F(l,r,INF) ,即從第一個開始就被卡住上限。
S ( l , r ) = i = l r d i S(l,r)=\sum\limits_{i=l}^{r}d_i

那麼有結論, F ( l , r , x ) = min ( G ( l , r ) , x + S ( l , r ) ) F(l,r,x)=\min(G(l,r),x+S(l,r))
相當於是分中途是否有卡住上限來討論。
容易讓人疑惑的是,如果第一個沒被卡住,後面被卡住了,這種情況算不到。
其實不會,第一個沒被卡住,後面都會被卡住,那麼如果第一個被卡住,後面肯定更大,更會被卡住,因此計算結果是一樣的。

有了這個結論,我們考慮分塊。
對於每一個詢問,兩邊的散塊顯然可以暴力計算,考慮中間的整塊。

對於第j塊,設它的左右邊界分別為 L j , R j L_j,R_j

考慮計算左右端點都在第j塊中的子區間貢獻。

顯然,對於兩個子區間 [ u 1 , v 1 ] , [ u 2 , v 2 ] [u_1,v_1],[u_2,v_2] ,若 S ( u 1 , v 1 ) &gt; S ( u 2 , v 2 ) S(u_1,v_1)&gt;S(u_2,v_2) G ( u 1 , v 1 ) &gt; G ( u 2 , v 2 ) G(u_1,v_1)&gt;G(u_2,v_2) ,那 [ u 2 , v 2 ] [u_2,v_2] 就沒用了

我們將這n個子區間( n 2 {\sqrt n}^2 )拉出來按照S排序,類似單調棧維護,這樣G就是遞減的
那麼要求 m i n ( S + x , G ) min(S+x,G) 的最大值,直接二分G這條線和S這條線的交點(S第一個大於G的位置)就行了。

考慮跨塊的情況
假設我們已經知道了第j-1塊能向後貢獻的最大值為C(即第j塊初始開頭最大是多少)

我們現在需要知道的是,結尾在第j塊中的最大值,以及第j塊能貢獻給下一塊的最大值C是多少

那麼我們可以維護一個字首單調棧和字尾單調棧,類似上面的,對於字首單調棧,我們想找到 m i n ( S + C , G ) min(S+C,G) 的最大值,同樣二分。

對於對下一塊的貢獻,要麼是以C為初始走過整一塊,要麼是從這塊中某個位置開始以x為初始,要麼直接以x為初始。第一第三種情況直接計算,第二種情況同樣在字尾單調棧中二分,最後三者取最大值就是對下一塊的最大貢獻。

時間複雜度 O ( n n log n + q n log n ) O(n\sqrt n\log n+q\sqrt n\log n)
如果改用桶排,則可以優化到 O ( n n + q n log n ) O(n\sqrt n+q\sqrt n\log n)

根據平衡規劃的思想,如果把塊大小開到 n log n \sqrt {n\log n} ,那麼複雜度可以到 O ( ( n + q ) n log n ) O((n+q)\sqrt{n\log n})
沒寫不知道。。。

反正直接第一種也跑的賊快

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]