1. 程式人生 > >JZOJ-senior-5936. 【NOIP2018模擬10.29】逛公園

JZOJ-senior-5936. 【NOIP2018模擬10.29】逛公園

Time Limits: 2000 ms Memory Limits: 524288 KB

Description

策策由於在noip2017考試當天去逛公園了,沒能出現在考場上,轉眼到了noip2018,策策的公園也悄然轉變,策策能否克服誘惑,成功坐在考場上呢?

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

Input

第一行兩個數n,q 表示景點序列長度 和 詢問個數
第二行n個數 表示di
第三行n個數 表示li
接下來q行,每行3個數:
表示 l ,r ,x0
下標均從1開始

Output

共q行,每行1個數表示愉悅度的最大值

Sample Input

6 3
0 5 3 2 0 4
8 10 8 1 9 9
1 3 9
2 6 3
3 4 0

Sample Output

10
8
3

樣例說明
詢問1 初始愉悅度9 只逛第2個公園 9+5=14 大於l2 ans=10
詢問2 初始愉悅度3 從2逛到3 3+5+3=11 大於l3 ans=8
詢問3 初始愉悅度0 只逛第3個公園 ans=3

Data Constraint

在這裡插入圖片描述

Solution

  • 50%:
  • 對於每個點,要是走它能使答案更優,那麼走,否則就以初始值從下一個點開始走
  • 時間複雜度 O ( q n )
    O(q*n)
  • 100%:
  • 定義一些東西:
  • F ( i , j , x 0 ) F(i,j,x_0) 表示以初始值 x 0 x0 經過 l l 到達 r r 後的答案
  • G ( i , j ) = F ( i , j , i n f ) G(i,j)=F(i,j,inf) ( i n f = inf= +∞)
  • S ( i , j ) S(i,j) 表示 i i j j D D 之和
  • 發現兩個重要的性質(這題最關鍵的地方)

  • 1.對於 a > = b a>=b F ( i , j , a ) > = F ( i , j , b ) F(i,j,a)>=F(i,j,b)
  • 2. F ( i , j , x 0 ) = m i n ( G ( i , j ) , x 0 + S ( i , j ) ) F(i,j,x_0)=min(G(i,j),x_0+S(i,j))
  • 推論
  • 對於詢問的 l , r l,r ,如果兩個子串都在 [ l , r ] [l,r] 中,且 G 1 > = G 2 G1>=G2 S 1 > = S 2 S1>=S2
  • 那麼第二個子串是一定不會取到的(由性質二得到)
  • 於是考慮分塊
  • 先考慮塊內的貢獻

  • 每塊大小為 n \sqrt{n} ,子串個數就是 O ( n ) O(n)
  • 我們可以先將每一塊中子串的 G G S S 預處理出來
  • 根據推論,用單調棧將沒用的子串扔掉
  • 那麼剩下的序列就是個 G G 不斷減小, S S 不斷增大的序列
  • 對於每次的詢問 x 0 x_0 ,最大的點一定是 m i n min 函式中間的某處
  • m i n min 函式前半部分遞減,後半部分遞增,看成兩條直線,則相交處將有最大值)
  • 二分就可以得到最大的答案啦
  • 再考慮塊間的貢獻

  • 兩種情況:
  • 1.當前塊開始,後面某塊結束
  • 2.前面某塊開始,當前塊結束
  • 參考 50% 的暴力策略,用 Y Y 代表上一個塊給這一個塊帶來的貢獻,當然是越大越好
  • (每個塊中的字首和字尾可以在處理子串的同時預處理)
  • 利用字首和 Y Y ,可採用和塊內貢獻相同的二分方法計算答案
  • 利用字尾,同理也可計算這一塊可以提供給下一塊的 Y Y
  • 分三種情況討論
  • 1.從上一個 Y Y 走滿整塊
  • 2.從某個字尾走到末尾
  • 3.直接取 x 0 x0
  • 三者的最大值就是 Y Y
  • 時間複雜度 O ( n n + q n l o g n ) O(n*\sqrt{n}+q*\sqrt{n}*logn)
  • (code中的 S S G G 的單調性和題解中的相反,本質相同)
  • (結合code中的註釋體驗++)

Code

#include<algorithm>
#include<cstdio>
#include<cmath>

#define fo(i,a,b) for(int i=a;i<=b;++i)
#define fd(i,a,b) for(int i=a;i>=b;--i)

using namespace std;

const int N=4e4+5,M=2e2+5,inf=1e9;
int n,q,len,tot;
int a[N],s[N],up[N],be[N];
struct node{int g,s;}d[N],d1[M],d2[M];
node c[M][N],all[M],pre[M][M],suf[M][M];

bool cmp(node x,node y)
{
	return x.g<y.g||x.g==y.g&&x.s>y.s;
}

void get(node *d,int t,node *c)//stack g+ s-
{
	sort(d+1,d+1+t,cmp);
	int tp=0;
	fo(i,1,t) if(d[i].g>d[i-1].g)
	{
		while(tp&&c[tp].s<d[i].s) --tp;
		c[++tp]=d[i];
	}
	c[0].g=tp;
}

void prepare()
{
	fo(k,1,tot)
	{
		int st=(k-1)*len+1,en=min(k*len,n);
		int t=0,t1=0,t2=0;
		fo(i,st,en)
		{
			int now=inf;
			fo(j,i,en)
			{
				now=min(now+a[j],up[j]);
				d[++t]=(node){now,s[j]-s[i-1]};//each
				if(i==st) d1[++t1]=d[t];//prefix
				if(j==en) d2[++t2]=d[t];//suffix
			}
			if(i==st) all[k]=d[t];//a whole block
		}
		get(d,t,c[k]);
		get(d1,t1,pre[k]);
		get(d2,t2,suf[k]);
	}
}

int solve(node *b,int x0)
{
	int l=1,r=b[0].g;
	while(r-l>1)
	{
		int mid=(l+r)>>1;
		if(b[mid].g<x0+b[mid].s) l=mid;
			else r=mid;
	}
	return max(min(b[l].g,x0+b[l].s),min(b[r].g,x0+b[r].s));
}

int main()
{
	freopen("park.in","r",stdin);
	freopen("park.out","w",stdout);
	scanf("%d%d",&n,&q);
	len=sqrt(n),tot=(n-1)/len+1;
	fo(i,1,n) scanf("%d",&a[i]),s[i]=s[i-1]+a[i];
	fo(i,1,n) scanf("%d",&up[i]),be[i]=(i-1)/len+1;
	
	prepare();
	
	while(q--)
	{
		int l,r,x;
		scanf("%d%d%d",&l,&r,&x);
		int st=be[l],en=be[r],ans=x,y=x;
		fo(i,l,min(r,st*len))
		{
			y=max(x,min(y+a[i],up[i]));
			ans=max(ans,y);
		}
		fo(i,st+1,en-1)
		{