5936. 【NOIP2018模拟10.29】逛公园

题目大意

给定一个 n n n个点的序列,每个点有 a i a_i ai l i l_i li两个信息, a i a_i ai表示可以获得的贡献, l i l_i li表示获得贡献的上限.
q q q个询问,每个询问 l , r , x 0 l,r,x0 l,r,x0,表示从 l l l r r r这个区间,选择一个子区间,从子区间的左端走到右端,获得的贡献最大.(子区间可以为空)
要求你输出最大可以获得的贡献

数据范围

对于 100 % 100\% 100%的数据
n , q &lt; = 40000 、 a i &lt; = 10000 、 l i &lt; = 1000000 n,q&lt;=40000、a_i&lt;=10000、l_i&lt;=1000000 n,q<=40000ai<=10000li<=1000000

Solution

原题bzoj2122
对于这种看着很难做的题首先考虑性质,先寻找性质再寻找方法.

F ( i , j , x 0 ) F(i,j,x0) F(i,j,x0)表示以 x 0 x0 x0为初始值,从 i i i走到 j j j的答案,有两个性质
1.若 a &gt; = b a&gt;=b a>=b,则 F ( i , j , a ) &gt; = F ( i , j , b ) F(i,j,a) &gt;= F(i,j,b) F(i,j,a)>=F(i,j,b)
2.设 G ( i , j ) G(i,j) G(i,j) F ( i , j , I N F ) F(i,j,INF) F(i,j,INF), S ( i , j ) S(i,j) S(i,j) i i i j j j的贡献之和,则 F ( i , j , x 0 ) = m i n ( G ( i , j ) , S ( i , j ) + x 0 ) F(i,j,x0)=min(G(i,j),S(i,j) + x0) F(i,j,x0)=min(G(i,j),S(i,j)+x0)
第一个性质显然,第二个性质分两种情况来讨论即可(没被卡住,被卡住).
推论:
对于一个询问 l . . r l..r l..r,若两个子串都被包含在 l l l, r r r内,且 G 1 &gt; = G 2 G1&gt;=G2 G1>=G2,并且 S 1 &gt; = S 2 S1&gt;=S2 S1>=S2,那么第二个子串就是无效的,由性质二可知.
于是可以考虑分块.
先预处理每个块的信息,包括 G , S G,S G,S,前缀的 G , S G,S G,S,后缀的 G , S G,S G,S.(排序+单调栈)
考虑计算贡献
块内贡献,直接二分计算
块间贡献:
维护一个Y值,代表上个块能给当前块带来的最大贡献.
贡献的计算:把Y值跟前缀一起二分计算.
Y值的维护:
共三种情况
1.走完整个块(预处理的时候维护一下)
2.从某个后缀开始(二分计算)
3.x0
三者最大值即是Y
复杂度 O ( n ∗ n + q ∗ n   ∗ l o g n ) O(n*\sqrt{n}+q*\sqrt{n} \ *log_n) O(nn +qn  logn)
可能常数有点大.

Codes

#include <cstdio>
#include <climits>
#include <cmath>
#include <algorithm>
#include <cstring>
#define F f[num]

using namespace std;

const int N = 4e4 + 5,M = 2e2 + 5,INF = 0x3f3f3f3f;

struct node
{
	int g,s;
}b[N],sta[N],sta1[N];

struct Block
{
	int L,R,top,pres,preg,pretop,suftop;
	node stk[N],pre[M],suf[M];
}f[M];

int block,n,q,tot,tail,top,l,r,r0,ans,sum,Y,Y1,Y2,ret,top1,gg,ss;
int be[N],a[N],lim[N],G[M][M],S[M][M];

int cmp(node x,node y)
{
	return x.g < y.g;
}

void Pre()
{
	for (int num = 1 ; num <= tot ; num++)
	{
		int l_ = F.L,r_ = F.R,K = F.L - 1;
		tail = 0;
		memset(G,0x3f,sizeof(G));
		for (int i = l_ ; i <= r_ ; i++)
		{
			for (int j = i ; j <= r_ ; j++) 
			{
				G[i - K][j - K] = min(lim[j],G[i - K][j - K - 1] + a[j]),S[i - K][j - K] = S[i - K][j - K - 1] + a[j];
				b[++tail].g = G[i - K][j - K];
				b[tail].s = S[i - K][j - K];
			}
		}
		for (int i = l_ ; i <= r_ ; i++) F.pre[i - K].g = G[1][i - K],F.pre[i - K].s = S[1][i - K];
		for (int i = r_ ; i >= l_ ; i--) F.suf[i - K].g = G[i - K][r_ - K],F.suf[i - K].s = S[i - K][r_ - K];
		F.pres = F.pre[r_ - K].s,F.preg = F.pre[r_ - K].g;
		sort(F.pre + 1,F.pre + F.R - F.L + 2,cmp);
		sort(F.suf + 1,F.suf + F.R - F.L + 2,cmp);
		sort(b + 1,b + tail + 1,cmp);
		for (int i = 1 ; i <= tail ; i++)
		{
			while (F.stk[F.top].s < b[i].s && F.top) F.top--;
			F.stk[++F.top].s = b[i].s;
			F.stk[F.top].g = b[i].g;
		}
		top = top1 = 0;
		memset(sta,0,sizeof(sta));
		memset(sta1,0,sizeof(sta1));
		for (int i = 1 ; i <= F.R - F.L + 1 ; i++)
		{
			while (sta[top].s < F.pre[i].s && top) top--;
			sta[++top].s = F.pre[i].s;
			sta[top].g = F.pre[i].g;
			while (sta1[top1].s < F.suf[i].s && top1) top1--;
			sta1[++top1].s = F.suf[i].s;
			sta1[top1].g = F.suf[i].g;
		}
		F.pretop = top,F.suftop = top1;
		memcpy(F.pre,sta,sizeof(F.pre));
		memcpy(F.suf,sta1,sizeof(F.suf));
	}
}

int find(int num,int val)
{
	int l = 1,r = F.top,Ans = 0;
	while (l <= r)
	{
		int mid = l + r >> 1;
		(F.stk[mid].g >= F.stk[mid].s + val) ? Ans = mid,r = mid - 1 : l = mid + 1;
	}
	return (!Ans) ? F.stk[r].g : max(F.stk[Ans - 1].g,F.stk[Ans].s + val);
}

int find1(int num,int val)
{
	int l = 1,r = F.pretop,Ans = 0;
	while (l <= r)
	{
		int mid = l + r >> 1;
		(F.pre[mid].g >= F.pre[mid].s + val) ? Ans = mid,r = mid - 1 : l = mid + 1;
	}
	return (!Ans) ? F.pre[r].g : max(F.pre[Ans - 1].g,F.pre[Ans].s + val);
}

int find2(int num,int val)
{
	int l = 1,r = F.suftop,Ans = 0;
	while (l <= r)
	{
		int mid = l + r >> 1;
		(F.suf[mid].g >= F.suf[mid].s + val) ? Ans = mid,r = mid - 1 : l = mid + 1;
	}
	return (!Ans) ? F.suf[r].g : max(F.suf[Ans - 1].g,F.suf[Ans].s + val);
}

int main()
{
	scanf("%d%d",&n,&q);
	block = sqrt(n);
	for (int i = 1 ; i <= n ; i++) scanf("%d",&a[i]);
	for (int i = 1 ; i <= n ; i++) scanf("%d",&lim[i]),be[i] = (i - 1) / block + 1;
	tot = (n - 1) / block + 1;
	for (int i = 1 ; i <= tot ; i++) f[i].L = (i - 1) * block + 1,f[i].R = min(i * block,n);
	Pre();
	while (q--)
	{
		scanf("%d%d%d",&l,&r,&r0);
		Y = ans = sum = r0;
		if (be[l] == be[r])
		{
			sum = r0;
			for (int i = l ; i <= r ; i++) sum += a[i],sum = min(sum,max(lim[i],r0)),ans = max(ans,sum);
			printf("%d\n",ans);
			continue;
		}
		for (int i = l ; i <= f[be[l]].R ; i++) 
			sum += a[i],sum = min(sum,max(lim[i],r0)),ans = max(ans,sum),Y += a[i],Y = min(Y,max(lim[i],r0));
		for (int i = be[l] + 1 ; i <= be[r] - 1 ; i++)
		{
			ret = find(i,r0);
			ans = max(ans,ret);
			ret = find1(i,Y);
			ans = max(ans,ret);
			Y1 = min(Y + f[i].pres,f[i].preg);
			Y2 = find2(i,r0);
			Y = max(r0,max(Y1,Y2));
		}
		sum = max(r0,Y);
		for (int i = f[be[r]].L ; i <= r ; i++) sum += a[i],sum = min(sum,max(lim[i],r0)),ans = max(ans,sum);
		printf("%d\n",ans);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值