CF453E Little Pony and Lord Tirek 主席树 set

12 篇文章 0 订阅
3 篇文章 0 订阅

题目链接

题意:
你有 n n n个东西,每个东西一开始有一个值,每个东西每一秒它的值会增加一个固定的量(可能是 0 0 0),并且每个东西有一个上限,它的值增长到上限就不再增长。你有 m m m次询问,每次在某一个时刻询问一个区间 [ l , r ] [l,r] [l,r]的所有东西的值之和,并且在询问结束时将这个区间内的所有数清零。保证询问的时刻是单调递增的。 n , m < = 1 e 5 n,m<=1e5 n,m<=1e5

题解:
我们首先先考虑这样一个子问题:在原题意的基础上,询问完了并不清零,应该怎么做。我们要知道一个区间里哪些是到了上界的,哪些是用原来数值加上增长量的。我们的方法是建出主席树,树的下标表示时间,建树之前要先离散化,这个题的离散化要注意一下,有些地方还是有一点细节的。

我们先考虑到上界的部分。我们知道,一个东西到了上限就会停止增长,我们就在主席树上它长到上界之后的第一个时刻加上它的这个上界,这样这个区间的主席树的某一个前缀和就是到这个时刻所有到上界的之和。

然后再考虑怎么算那些区间里没到上限的数的和。对于一个东西,在它到达上限之前都是可以用初始值加上每秒增加量乘上秒数来算的。一个区间的初始值我们可以用一个前缀和来求出,那么我们用另一棵主席树来维护这个没有到上界的值。我们只需要维护这个区间内在某一个时刻没到上界的增加量之和,再乘上时间即可。而我们对于一个东西,我们在它长到上限之前,是可以算到这里面的。我们还是在对应的要长满的时刻加上它的增长量,询问的时间在这之前的情况下,这个东西就会有贡献。换句话说,对于一个时间,我们要询问的是一个后缀和,然后用这个后缀和乘上时间就可以了。然而思考一下不难发现,虽然两个主席树维护的东西各不相同,然而其实可以用同一棵主席树来记录信息,并且建树的时候都是那个要到上限的时刻,于是建树的时候也可以一起维护信息。

那么接下来就是考虑有清零怎么办。我们的思路是,用set来维护区间,set里的每一段区间表示的是清零时间相同的一段,由于只会有 m m m次操作,一开始只有 n n n个位置,所以set里面的元素个数不会超过 n + m n+m n+m量级。这里的区别是,我们在主席树里对应的时刻是直接用上限除以生长速度的,而我们记录是否是一开始的有初始高度情况。对于一开始的有初始高度的都是只有一个元素的区间,可以特判。set的具体维护方法就是通过upper_bound之后一个一个往后遍历来找所有要用到的区间,对于每个区间分别求答案。最后把这些区间从set里删掉,把当前这个询问变成一个新的区间加进去,因为这整个区间上一次改为 0 0 0的时间变成了一样的。思路差不多就说到这里,具体看代码吧。

代码:

#include <bits/stdc++.h>
using namespace std;

int n,qq,root[200010],cnt,shu,num;
long long q[200010],m[200010],h[200010],ans,ti[200010];
struct node//¼Ç¼һÆð±ä³É0µÄÇø¼äµÄÐÅÏ¢ 
{
	int l,r,ji;//×óÓҶ˵㣬Èç¹ûÓв»ÊǵÚÒ»´ÎÐ޸ĵģ¬jiΪËüµÄ³õʼֵ 
	long long t;//t±íʾÉÏ´ÎÐ޸ĵÄʱ¼ä 
}a[200010];
struct tree
{
	int l,r;
	long long s,s1;
}tr[8000010];
set<node> s;
vector<node> v;
bool operator < (node x,node y)
{
	return x.l<y.l;
}
inline int read()
{
	int x=0;
	char s=getchar();
	while(s>'9'||s<'0')
	s=getchar();
	while(s>='0'&&s<='9')
	{
		x=x*10+s-'0';
		s=getchar();
	}
	return x;
}
inline void update(int &rt,int rt1,int l,int r,int x,int i)
{
	rt=++cnt;
	tr[rt]=tr[rt1];
	tr[rt].s+=m[i];
	tr[rt].s1+=h[i];
	if(l==r)
	return;
	int mid=(l+r)>>1;
	if(x<=mid)
	update(tr[rt].l,tr[rt1].l,l,mid,x,i);
	else
	update(tr[rt].r,tr[rt1].r,mid+1,r,x,i);
}
inline long long query1(int rt,int rt1,int l,int r,int le,int ri)
{
	if(le<=l&&r<=ri)
	return tr[rt].s-tr[rt1].s;
	int mid=(l+r)>>1;
	long long res=0;
	if(le<=mid)
	res+=query1(tr[rt].l,tr[rt1].l,l,mid,le,ri);
	if(mid+1<=ri)
	res+=query1(tr[rt].r,tr[rt1].r,mid+1,r,le,ri);
	return res;
}
inline long long query2(int rt,int rt1,int l,int r,int le,int ri)
{
	if(le<=l&&r<=ri)
	return tr[rt].s1-tr[rt1].s1;
	int mid=(l+r)>>1;
	long long res=0;
	if(le<=mid)
	res+=query2(tr[rt].l,tr[rt1].l,l,mid,le,ri);
	if(mid+1<=ri)
	res+=query2(tr[rt].r,tr[rt1].r,mid+1,r,le,ri);
	return res;
}
int main()
{
	n=read();
	for(int i=1;i<=n;++i)
	{
		q[i]=read();
		m[i]=read();
		h[i]=read();
		if(h[i])
		ti[i]=(m[i]-1)/h[i]+1;
		else
		ti[i]=1e10;		
	}
	sort(ti+1,ti+n+1);
	shu=unique(ti+1,ti+n+1)-ti-1;
	for(int i=1;i<=n;++i)
	{
		int ji;
		if(h[i])
		ji=lower_bound(ti+1,ti+shu+1,(m[i]-1)/h[i]+1)-ti;
		else
		ji=shu;
		update(root[i],root[i-1],1,shu,ji,i);
	}
	for(int i=1;i<=n;++i)
	{
		node x;
		x.l=i;
		x.r=i;
		x.ji=q[i];
		x.t=0;
		s.insert(x);
	}
	qq=read();
	for(int qwq=1;qwq<=qq;++qwq)
	{
		int t=read(),l=read(),r=read();
		set<node>::iterator it;
		node x;
		x.ji=0;
		x.l=l;
		x.r=r;
		x.t=t;
		it=s.upper_bound(x);
		--it;
		num=0;
		while(1)
		{
			if((*it).l>r||it==s.end())
			break;
			v.push_back((*it));
			++num;
			++it;
		}
		for(int i=0;i<num;++i)
		s.erase(s.find(v[i]));
		if(v[0].l<l)
		{
			node y;
			x=v[0];
			y.ji=x.ji;
			y.l=x.l;
			y.r=l-1;
			y.t=x.t;
			s.insert(y);
			v[0].l=l;
		}
		if(v[num-1].r>r)
		{
			node y;
			x=v[num-1];
			y.ji=x.ji;
			y.l=r+1;
			y.r=x.r;
			y.t=x.t;
			s.insert(y);
			v[num-1].r=r;
		}
		x.ji=0;
		x.l=l;
		x.r=r;
		x.t=t;
		s.insert(x);
		ans=0;
		for(int i=0;i<num;++i)
		{
			if(v[i].t)
			{
				int ji=upper_bound(ti+1,ti+shu+1,t-v[i].t)-ti-1;
				if(ji!=0)
				ans+=query1(root[v[i].r],root[v[i].l-1],1,shu,1,ji);
				ans+=query2(root[v[i].r],root[v[i].l-1],1,shu,ji+1,shu)*(t-v[i].t);
			}
			else
			ans+=min(m[v[i].l],q[v[i].l]+t*h[v[i].l]);			
		}		
		v.clear();
		printf("%lld\n",ans);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值