P4970 全村最好的嘤嘤刀(树状数组与线段树的梦幻联动)

23 篇文章 2 订阅
16 篇文章 0 订阅
题目描述

重阳节到了,我们最好的八重樱拥有全村最好的嘤嘤刀……
在绯玉丸力量的影响下,八重村成了一条长度为 n 的八重街,并且绯玉丸可以带着八重樱出现在街上的任意地点。而我们的八重樱则会在街上任意穿梭来获取某一地点上的嘤嘤嘤能量,用以升级她的嘤嘤刀。
在每个时刻,都会发生以下 3 个事件:
1 x val 表示在 x 地点出现了携带着 val 点嘤嘤嘤能量的绯狱丸,并且绯狱丸会吞噬该点的嘤嘤嘤能量,使得该点的嘤嘤嘤能量变为 val−ai​点,ai为出现绯狱丸的前一刻,该点所存在的嘤嘤嘤能量。
2 l r 表示绯玉丸会带着八重樱出现在[ l , r ]间的任意一点。八重樱为了尽快升级她的嘤嘤刀,会获取该区间上最大的嘤嘤嘤能量。特殊的,为了保卫八重村,当 l , r 之间存在绯狱丸时,八重樱会优先用她的嘤嘤刀对付绯狱丸,并获得绯狱丸此时拥有的 ai点嘤嘤嘤能量。
3 l r val 绯玉丸会嘤嘤嘤,使得[ l , r ]上的每一个地点的嘤嘤嘤能量增加 val 点(包括绯狱丸)。

输入格式

第一行为 2 个数 n , m。
第二行为 n 个数,分别表示八重街上每个地点的初始嘤嘤嘤能量。
接下来 m 行,每行会发生 3 个事件中的一个,输入格式为题目描述中的格式。

输出格式

对于每一个事件 2 ,你应当输出八重樱在该事件中获取的嘤嘤嘤能量并换行。
当所有事件结束时,如果嘤嘤刀积累的能量小于 10000 ,你应当输出 QAQ 。
如果在[ 10000 , 10000000 )间,你应当输出Sakura。
如果都不符合,请输出 ice。

样例

输入 #1
10 10
1 2 3 4 5 6 7 8 9 10
2 1 10
2 1 10
2 1 10
2 1 10
2 1 10
2 1 10
2 1 10
2 1 10
2 1 10
2 1 10
输出 #1
10
9
8
7
6
5
4
3
2
1
QAQ
输入 #2
10 11
0 0 0 0 0 0 0 0 0 0
3 1 10 1
3 2 10 1
3 3 10 1
3 4 10 1
3 5 10 1
3 6 10 1
3 7 10 1
3 8 10 1
3 9 10 1
3 10 10 1
2 1 10
输出 #2
10
QAQ
输入 #3
10 13
0 0 0 0 0 0 0 0 0 0
1 10 10000
1 9 9000
1 8 8000
1 7 7000
1 6 6000
1 5 5000
1 4 4000
1 3 3000
1 2 2000
1 1 1000
2 10 10
2 8 8
2 8 10
输出 #3
10000
8000
9000
Sakura

说明/提示

对于所有的数据:
最终答案都会在 [0,231−1] 范围内;
n , m ⩽ 100000。
值得注意的是,无论八重樱是获取了某一地点的嘤嘤嘤能量还是击败了某一地点的绯狱丸,该地点的嘤嘤嘤值都应当清零而不是保留原来的数值。
对于事件 2 ,题目保证每个事件中最多出现 1 只绯狱丸。如果出现多个最大值,在每次比较时,请选择靠右的(std默认的)。

题目分析

这道题的难点在于操作1+2,操作2/3 区间最大值查询和区间上统一加值都是线段树的基本操作,没有什么难度。

先说这道题的线段树用法:线段树要完成后两个操作,因此要有 max维护区间最大值懒标记add完成区间加数。因为每次我们到达一个地方后会吸收这个地方的能量,使得该点能量清0,因此还需要加一个 pos记录最大值的位置
对于操作1,我们要记录飞鱼丸的位置。如果用线段树记录,后面查询时还要优先查询这个区间内的飞鱼丸的位置,不好操作。因此我们可以把飞鱼丸的位置记录独立出来,用树状数组来记录。
假设飞鱼丸的位置是x,那么我们可以在树状数组的x位置插入x(因为题目保证了每次查询的区间内至多有一个飞鱼丸),这样就可以在查询时直接查询到飞鱼丸的位置,然后只删除该位置即可。
这样就解决了操作1+2的问题,剩下的操作2/3都是线段树的基础操作,就不详细说了。

注意: 关本题的最终答案范围在[0,232−1],是真的让你开int,虽然答案确实会爆int,但std让他自然溢出了,于是乎可能你输出了ice,但正确输出是QAQ

代码如下
#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <stack>
#include <map>
#include <queue>
#include <vector>
#include <set>
#include <algorithm>
#include <iomanip>
#define LL long long
#define PII pair<int,int>
using namespace std;
const int N=1e5+5;
struct Node{
	int l,r;
	int max,pos,add;	//线段树维护的三个辅助信息(含义在上面说了)
}tr[N*4];
int n,m;
int a[N];
LL tri[N];			//树状数组维护飞鱼丸的位置
int lowbit(int x)	//树状数组三个模板函数
{
    return x & -x;
}
void add(int x,int c)
{
    for(int i=x;i<=n;i+=lowbit(i))
        tri[i]+=c;
}
LL sum(int x)
{
    LL res=0;
    for(int i=x;i>0;i-=lowbit(i))
        res+=tri[i];
    return res;
}
void pushup(Node &u,Node &left,Node &right)		//通过子段来求父段信息
{
    if(left.max>right.max)
	{
	    u.max=left.max;
	    u.pos=left.pos;
	}
	else
	{
	    u.max=right.max;
	    u.pos=right.pos;
	}
}
void pushup(int u)		//包装一下,简化写法
{
	pushup(tr[u],tr[u<<1],tr[u<<1|1]);
}
void pushdown(int u)	//通过父段信息求子段信息(主要是为了传递懒标记)
{
	Node &root=tr[u],&left=tr[u<<1],&right=tr[u<<1|1];
	left.add+=root.add;
	left.max+=root.add;
	right.add+=root.add;
	right.max+=root.add;
	root.add=0;
}
void build(int u,int l,int r)	//建树(模板)
{
	if(l==r) tr[u]={l,r,a[l],l,0};
	else
	{
		tr[u]={l,r};
		int mid=l+r>>1;
		build(u<<1,l,mid),build(u<<1|1,mid+1,r);
		pushup(u);
	}
}
void modify(int u,int x,int v)	//单点修改(模板)
{
    if(tr[u].l==x&&tr[u].r==x) tr[u].max=v-tr[u].max;//把x位置的数改为v-a[x](根据题意)
    else
    {
        pushdown(u);
        int mid=tr[u].l+tr[u].r>>1;
        if(mid>=x) modify(u<<1,x,v);
        else modify(u<<1|1,x,v);
        pushup(u);
    }
}
void update(int u,int l,int r,int v)	//区间加数(模板)
{
	if(l<=tr[u].l&&tr[u].r<=r)
	{
		tr[u].add+=v;
		tr[u].max+=v;
	}
	else
	{
		pushdown(u);
		int mid=tr[u].l+tr[u].r>>1;
		if(l<=mid) update(u<<1,l,r,v);
		if(r>mid) update(u<<1|1,l,r,v);
		pushup(u);
	}
}
Node query(int u,int l,int r)	//区间查询,因为需要区间的最大值及位置,所以直接返回Node类型
{
	if(l<=tr[u].l&&tr[u].r<=r) return tr[u];
	
	pushdown(u);
	int mid=tr[u].l+tr[u].r>>1;
	if(mid>=r) return query(u<<1,l,r);			//[l,r]只涉及左子段的情况
	else if(mid<l) return query(u<<1|1,l,r);	//[l,r]只涉及右子段的情况
	else 				//[l,r]两子段都涉及的情况
	{
	    Node left=query(u<<1,l,r);		//递归得到需要的左右区间信息
        Node right=query(u<<1|1,l,r);
        Node res;
        pushup(res,left,right);			//用pushup进行合并
        return res;
	}
}
int main()
{
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	
	build(1,1,n);
	int ans=0;
	while(m--)
	{
		int op,l,r,val;
		scanf("%d %d %d",&op,&l,&r);
		if(op==1)				//操作1:树状数组记录飞鱼丸位置,然后进行单点修改
		{
			add(l,l);
			modify(1,l,r);
		}
		else if(op==2)	//操作2,优先查询区间内是否存在飞鱼丸,存在则删除飞鱼丸位置,否则先区间查询最大值及位置,再进行删除
		{
		    int has=sum(r)-sum(l-1);	//查询是否存在飞鱼丸
		    Node t;
		    if(has) t=query(1,has,has);	//存在则拿飞鱼丸位置信息
		    else t=query(1,l,r);		//否则拿最大值位置信息
		    
			ans+=t.max;
			printf("%d\n",t.max);
			update(1,t.pos,t.pos,-t.max);	//删除操作
			if(has) add(has,-has);			//树状数组内位置也要删
		}
		else							//操作3:区间加数
		{
			scanf("%d",&val);
			update(1,l,r,val);
		}
	}
	if(ans<10000) puts("QAQ");
	else if(ans<10000000) puts("Sakura");
	else puts("ice");
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lwz_159

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值