[CF453E]Little Pony and Lord Tirek

题意:有$n$个马,第$i$个马有初始能量$s_i$,每单位时间增加$r_i$,上限是$m_i$,多组询问$(t,l,r)$询问在时间$t$时$[l,r]$的能量和,询问完后把这段区间清零,保证$t$递增

两个月前曾经看到过这个题,当时看了官方题解+看了某神犇的中文题解之后觉得是不可做题所以就鸽了,今天回来一看原来还有这么简单的做法

直接上线段树,每个线段树节点存①最后被访问的时间$last$,没访问过记为$-2$,之前在不同时间被深♂入访问过记为$-1$②当前区间内的所有马,按照从零开始需要多少时间到达上限从小到大排序③$m$和$r$的前缀和

建树直接建,排个序然后处理前缀和,标记记为$-2$

询问时,设访问时间为$T$,访问到节点$x$,代表区间$[l,r]$

①若$last_x=-1$或询问区间未完整包含$[l,r]$,直接递归左右两边处理

②若$last_x=-2$,暴力计算答案,更新$last_x=T$

③若$last_x\geq0$,在当前区间内二分找到最后一个在$T-last_x$时间内会到达上限的马,前半部分答案为$\sum m$,后半部分答案为$\left(T-last_x\right)\sum r$,最后更新$last_x=T$

pushup:如果两个儿子标记相同,复制上来,否则记当前节点的标记为$-1$

pushdown:如果当前节点标记$\geq0$,直接往下覆盖标记

核心思想就是:除了第一次,后面每一次都可以当成初始能量为$0$来做,询问完后它又自己变回$0$了

开始处理询问后不会再有点的标记变为$-2$,所以暴力计算初始时的答案是可行的

#include<stdio.h>
#include<algorithm>
using namespace std;
#define ll long long
#define inf 2147483647
struct data{
	int s,m,r,t;
	data(int x=0){t=x;}
	void gao(){
		scanf("%d%d%d",&s,&m,&r);
		t=(r?m/r+(m%r>0):inf);
	}
	ll calc(ll u){return min(s+r*u,(ll)m);}
}a[18][100010];
int Tt[400010];
ll Tm[18][400010],Tr[18][400010];
bool operator<(data a,data b){return a.t<b.t;}
void build(int l,int r,int x,int d){
	Tt[x]=-2;
	if(l==r){
		a[d][l].gao();
		Tm[d][l]=Tm[d][l-1]+a[d][l].m;
		Tr[d][l]=Tr[d][l-1]+a[d][l].r;
		return;
	}
	int mid=(l+r)>>1,i;
	build(l,mid,x<<1,d+1);
	build(mid+1,r,x<<1|1,d+1);
	for(i=l;i<=r;i++)a[d][i]=a[d+1][i];
	sort(a[d]+l,a[d]+r+1);
	for(i=l;i<=r;i++){
		Tm[d][i]=Tm[d][i-1]+a[d][i].m;
		Tr[d][i]=Tr[d][i-1]+a[d][i].r;
	}
}
void pushdown(int x){
	if(Tt[x]>=0)Tt[x<<1]=Tt[x<<1|1]=Tt[x];
}
void pushup(int x){
	Tt[x]=Tt[x<<1];
	if(Tt[x]!=Tt[x<<1|1])Tt[x]=-1;
}
ll query(int T,int L,int R,int l,int r,int x,int d){
	int i;
	ll res=0;
	if(L<=l&&r<=R&&Tt[x]!=-1){
		if(Tt[x]==-2){
			for(i=l;i<=r;i++)res+=a[d][i].calc(T);
		}else{
			i=upper_bound(a[d]+l,a[d]+r+1,data(T-Tt[x]))-a[d]-1;
			res=(T-Tt[x])*(Tr[d][r]-Tr[d][i])+Tm[d][i]-Tm[d][l-1];
		}
		Tt[x]=T;
		return res;
	}
	pushdown(x);
	i=(l+r)>>1;
	if(L<=i)res+=query(T,L,R,l,i,x<<1,d+1);
	if(i<R)res+=query(T,L,R,i+1,r,x<<1|1,d+1);
	pushup(x);
	return res;
}
int main(){
	int n,m,t,l,r;
	scanf("%d",&n);
	build(1,n,1,0);
	scanf("%d",&m);
	while(m--){
		scanf("%d%d%d",&t,&l,&r);
		printf("%I64d\n",query(t,l,r,1,n,1,0));
	}
}

转载于:https://www.cnblogs.com/jefflyy/p/8335765.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值