5936. 【NOIP2018模拟10.29】逛公园

本文深入探讨了一种解决区间贡献最大化问题的算法,通过分析点序列的贡献与上限,利用分块、预处理和二分查找等技术,实现了在限定条件下的最优解计算。文章详细解释了算法的性质、解决方案及其实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意

给定一个nnn个点的序列,每个点有aia_iailil_ili两个信息,aia_iai表示可以获得的贡献,lil_ili表示获得贡献的上限.
qqq个询问,每个询问l,r,x0l,r,x0l,r,x0,表示从lllrrr这个区间,选择一个子区间,从子区间的左端走到右端,获得的贡献最大.(子区间可以为空)
要求你输出最大可以获得的贡献

数据范围

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

Solution

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

F(i,j,x0)F(i,j,x0)F(i,j,x0)表示以x0x0x0为初始值,从iii走到jjj的答案,有两个性质
1.若a&gt;=ba&gt;=ba>=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,INF)F(i,j,INF)F(i,j,INF),S(i,j)S(i,j)S(i,j)iiijjj的贡献之和,则F(i,j,x0)=min(G(i,j),S(i,j)+x0)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..rl..rl..r,若两个子串都被包含在lll,rrr内,且G1&gt;=G2G1&gt;=G2G1>=G2,并且S1&gt;=S2S1&gt;=S2S1>=S2,那么第二个子串就是无效的,由性质二可知.
于是可以考虑分块.
先预处理每个块的信息,包括G,SG,SG,S,前缀的G,SG,SG,S,后缀的G,SG,SG,S.(排序+单调栈)
考虑计算贡献
块内贡献,直接二分计算
块间贡献:
维护一个Y值,代表上个块能给当前块带来的最大贡献.
贡献的计算:把Y值跟前缀一起二分计算.
Y值的维护:
共三种情况
1.走完整个块(预处理的时候维护一下)
2.从某个后缀开始(二分计算)
3.x0
三者最大值即是Y
复杂度O(n∗n+q∗n ∗logn)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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值