【PA 2015】Siano

本文解析了一道关于区间加区间覆盖的经典算法题,详细介绍了如何通过预处理前缀和,结合二分查找和线段树技巧,解决农夫Byteasar在不同时间点对草场进行收割的问题。算法巧妙地利用了草生长速度的特性,实现了高效求解。

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

【题目】

题目描述:

农夫 Byteasar 买了一片 nnn 亩的土地,他要在这上面种草。

他在每一亩土地上都种植了一种独一无二的草,其中,第 iii 亩土地的草每天会长高 aia_iai 厘米。

Byteasar 一共会进行 mmm 次收割,其中第 iii 次收割在第 did_idi 天,并把所有高度大于等于 bib_ibi 的部分全部割去。Byteasar 想知道,每次收割得到的草的高度总和是多少,你能帮帮他吗?

输入格式:

第一行包含两个正整数 n,m(1≤n,m≤500000)n,m(1≤n,m≤500000)n,m(1n,m500000),分别表示亩数和收割次数。

第二行包含 nnn 个正整数,其中第 iii 个数为 ai(1≤ai≤1000000)a_i(1≤a_i≤1000000)ai(1ai1000000),依次表示每亩种植的草的生长能力。

接下来 mmm 行,每行包含两个正整数 di,bi(1≤di≤1012,0≤bi≤1012)d_i,b_i(1≤d_i≤10^{12},0≤b_i≤10^{12})di,bi(1di10120bi1012),依次描述每次收割。

数据保证 d1&lt;d2&lt;...&lt;dmd_1&lt;d_2&lt;...&lt;d_md1<d2<...<dm,并且任何时刻没有任何一亩草的高度超过 101210^{12}1012

输出格式:

输出 mmm 行,每行一个整数,依次回答每次收割能得到的草的高度总和。

样例数据:

输入
4 4
1 2 4 3
1 1
2 2
3 0
4 4

输出
6
6
18
0

提示:

111 天,草的高度分别为 1,2,4,31,2,4,31,2,4,3,收割后变为 1,1,1,11,1,1,11,1,1,1
222 天,草的高度分别为 2,3,5,42,3,5,42,3,5,4,收割后变为 2,2,2,22,2,2,22,2,2,2
333 天,草的高度分别为 3,4,6,53,4,6,53,4,6,5,收割后变为 0,0,0,00,0,0,00,0,0,0
444 天,草的高度分别为 1,2,4,31,2,4,31,2,4,3,收割后变为 1,2,4,31,2,4,31,2,4,3


【分析】

首先,我们要知道一个东西,即若 ai&gt;aja_i&gt;a_jai>aj,那么在任意时间,hih_ihi 是始终大于 hjh_jhj

那么我们不妨对数组 aaa 进行排序,排序后的草的高度在任意时间内是单调不减的

那么对于每次的询问 did_idi,所有草都长了 di−di−1d_i-d_{i-1}didi1 的时间,这个可以用预处理出前缀和,然后用区间加搞定,而对于 bib_ibi,由于此时高度一定是单调不减的,可以二分找出第一个要割的位置(在线段树上找),然后统计答案后用区间覆盖

所以这道题就转变成了区间加区间覆盖的简单问题(由于区间和只用返回 sumrootsum_{root}sumroot,连区间和都不用写)


【代码】

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 500005
#define ll long long
using namespace std;
int a[N];
ll d[N],b[N],s[N],sum[N<<2],Max[N<<2],add[N<<2],mark[N<<2];
void Pushup(int root)
{
	sum[root]=sum[root<<1]+sum[root<<1|1];
	Max[root]=max(Max[root<<1],Max[root<<1|1]);
}
void Cover(int root,int l,int r,ll val)
{
	Max[root]=val;
	sum[root]=val*(r-l+1);
	add[root]=0,mark[root]=val;
}
void Add(int root,int l,int r,ll val)
{
	add[root]+=val;
	Max[root]+=val*a[r];
	sum[root]+=val*(s[r]-s[l-1]);
}
void Pushdown(int root,int l,int r,int mid)
{
	if(mark[root]!=-1)  Cover(root<<1,l,mid,mark[root]),Cover(root<<1|1,mid+1,r,mark[root]);
	if(add[root])  Add(root<<1,l,mid,add[root]),Add(root<<1|1,mid+1,r,add[root]);
	add[root]=0,mark[root]=-1;
}
int find(int root,int l,int r,ll x)
{
	if(l==r)return l;
	int mid=(l+r)>>1;
	Pushdown(root,l,r,mid);
	if(x<=Max[root<<1])  return find(root<<1,l,mid,x);
	return find(root<<1|1,mid+1,r,x);
}
void Modify(int root,int l,int r,int x,int y,ll val)
{
	if(l>=x&&r<=y)
	{
		Cover(root,l,r,val);
		return;
	}
	int mid=(l+r)>>1;
	Pushdown(root,l,r,mid);
	if(x<=mid)  Modify(root<<1,l,mid,x,y,val);
	if(y>mid)  Modify(root<<1|1,mid+1,r,x,y,val);
	Pushup(root);
}
int main()
{
	int n,m,i;
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;++i)
	  scanf("%d",&a[i]);
	sort(a+1,a+n+1);
	for(i=1;i<=n;++i)
	  s[i]=s[i-1]+a[i];
	for(i=1;i<=m;++i)
	{
		scanf("%lld%lld",&d[i],&b[i]);
		Add(1,1,n,d[i]-d[i-1]);ll ans=sum[1];
		if(Max[1]<=b[i]){printf("0\n");continue;}
		int pos=find(1,1,n,b[i]);
		Modify(1,1,n,pos,n,b[i]);
		printf("%lld\n",ans-sum[1]);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值