Luogu 45887 全村最好的嘤嘤刀(线段树 树状数组)

Posted dxy0310

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Luogu 45887 全村最好的嘤嘤刀(线段树 树状数组)相关的知识,希望对你有一定的参考价值。

https://www.luogu.org/problemnew/show/T45887

 

题目背景

重阳节到了,我们最好的八重樱拥有全村最好的嘤嘤刀……

题目描述

在绯玉丸力量的影响下,八重村成了一条长度为 nnn 的八重街,并且绯玉丸可以带着八重樱出现在街上的任意地点。而我们的八重樱则会在街上任意穿梭来获取某一地点上的嘤嘤嘤能量,用以升级她的嘤嘤刀。

在每个时刻,都会发生以下 333 个事件:

111 xxx valvalval 表示在 xxx 地点出现了携带着 valvalval 点嘤嘤嘤能量的绯狱丸,并且绯狱丸会吞噬该点的嘤嘤嘤能量,使得该点的嘤嘤嘤能量变为 val−ai val - a_ivalai? 点,aia_iai? 为出现绯狱丸的前一刻,该点所存在的嘤嘤嘤能量。

222 lll rrr 表示绯玉丸会带着八重樱出现在[ lll , rrr ]间的任意一点。八重樱为了尽快升级她的嘤嘤刀,会获取该区间上最大的嘤嘤嘤能量。特殊的,为了保卫八重村,当 lll , rrr 之间存在绯狱丸时,八重樱会优先用她的嘤嘤刀对付绯狱丸,并获得绯狱丸此时拥有的 aia_iai? 点嘤嘤嘤能量。

333 lll rrr valvalval 绯玉丸会嘤嘤嘤,使得[ lll , rrr ]上的每一个地点的嘤嘤嘤能量增加 valvalval 点(包括绯狱丸)。

输入输出格式

输入格式:

 

第一行为 222 个数 nnn , mmm

第二行为 nnn 个数,分别表示八重街上每个地点的初始嘤嘤嘤能量。

接下来 mmm 行,每行会发生 333 个事件中的一个,输入格式为题目描述中的格式。

 

输出格式:

 

对于每一个事件 222 ,你应当输出八重樱在该事件中获取的嘤嘤嘤能量并换行。

当所有事件结束时,如果嘤嘤刀积累的能量小于 100001000010000 ,你应当输出 QAQQAQQAQ

如果在[ 100001000010000 , 100000001000000010000000 )间,你应当输出 SakuraSakuraSakura

如果都不符合,请输出 iceiceice

 

输入输出样例

输入样例#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][0,2^{31}-1][0,2311] 范围内;

nnn , mmm ?leqslant? 100000100000100000

值得注意的是,无论八重樱是获取了某一地点的嘤嘤嘤能量还是击败了某一地点的绯狱丸,该地点的嘤嘤嘤值都应当清零而不是保留原来的数值。

对于事件 222 ,题目保证每个事件中最多出现 111 只绯狱丸。

 

解题思路:

 

  看过题目之后就应该知道这题肯定与线段树有关。

  这题的难点应该是事件二,如何维护区间内是否有绯狱丸以及绯狱丸的位置,还有如何快速实现删除操作。

  对于事件1,:只是一个单点修改的操作,线段树维护就行了。

  对于事件3:只是一个区间加法的操作,线段树维护lazy标记,记得下传就行了。

  对于事件2:先考虑[x,y]区间内没有绯狱丸的情况,只需要查询区间最大值及其位置,至于删除无非就是将最大值删除,可以转换为将最大值所在位置清零,用单点修改便可实现。

  重点在于[x,y]区间有绯狱丸的情况,仔细阅读题目说明可以知道最多出现1只绯狱丸,由于我们是需要执行删除操作的,所以必须知道该绯狱丸所在的位置,这时我们就可以用树状数组来实现了,考虑用树状数组,在x位置加上值x,查询的时候至于要知道[x,y]区间的值是否为0即可,如果不为0,那么绯狱丸所在的位置便是查询的结果,再用线段树实现单点修改就行了,可谓妙啊!

   

  由于这题代码量较大,至今我的代码还没过 QWQ,所以只好贴标程了,自己的等A了之后再来填坑吧~~~

  思路是一致的,只是实现方面上略微不同。

标程:

  

技术分享图片
#include <iostream>
#include <cstdio>
using namespace std;

int n,m;
int last_ans=0;
int p,x,y,v;
int se_tree[100000+100];
int add[100000*4+10];//因为重载了max,所以延迟标记要单独出来

struct node
{
    int maxx;
    int max_place;
    friend node max(node a,node b)
    {
        return a.maxx>b.maxx ? a:b;
    }//重载max
} tree[100000*4+10];

inline int read()
{
    int ans;
    char c;
    bool op=false;
    while(c=getchar(),c<0||c>9)
    {
        if(c==-) op=true;
    }
    ans=c-0;
    while(c=getchar(),c>=0&&c<=9)
    {
        ans=ans*10+c-0;
    }
    return op? -ans:ans;
}

void build(int l,int r,int num)
{
    if(l==r)
    {
        tree[num].maxx=read();
        tree[num].max_place=l;
        return;
    }
    int mid=(l+r)>>1;
    build(l,mid,num<<1);
    build(mid+1,r,num<<1|1);
    tree[num]=max(tree[num<<1],tree[num<<1|1]);
}

inline void spread(int l,int r,int num)
{
    if(add[num])
    {
        int mid=(l+r)>>1;
        tree[num<<1].maxx+=add[num];
        tree[num<<1|1].maxx+=add[num];
        add[num<<1]+=add[num];
        add[num<<1|1]+=add[num];
        add[num]=0;
    }
}

void appear(int l,int r,int L,int val,int num)
{
    if(l==r)
    {
        tree[num].maxx=val-tree[num].maxx;
        return;
    }
    int mid=(l+r)>>1;
    spread(l,r,num);
    if(mid<L) appear(mid+1,r,L,val,num<<1|1);
    else appear(l,mid,L,val,num<<1);
    tree[num]=max(tree[num<<1],tree[num<<1|1]);
}

node ask(int l,int r,int L,int R,int num)
{
    if(l==L&&r==R)
    {
        return tree[num];
    }
    int mid=(l+r)>>1;
    spread(l,r,num);
    if(mid<L) return ask(mid+1,r,L,R,num<<1|1);
    else if(mid>=R) return ask(l,mid,L,R,num<<1);
    else return max(ask(l,mid,L,mid,num<<1),ask(mid+1,r,mid+1,R,num<<1|1));
}

void change(int l,int r,int L,int R,int val,int num)
{
    if(l==L&&r==R)
    {
        tree[num].maxx+=val;
        add[num]+=val;
        return;
    }
    int mid=(l+r)>>1;
    spread(l,r,num);
    if(mid<L) change(mid+1,r,L,R,val,num<<1|1);
    else if(mid>=R) change(l,mid,L,R,val,num<<1);
    else change(l,mid,L,mid,val,num<<1),change(mid+1,r,mid+1,R,val,num<<1|1);
    tree[num]=max(tree[num<<1],tree[num<<1|1]);
}

inline void se_add(int x,int val)
{
    for(; x<=n; x+=x&-x) se_tree[x]+=val;
}

inline int se_ask(int x)
{
    int ans=0;
    for(; x; x-=x&-x) ans+=se_tree[x];
    return ans;
}

int main()
{
    n=read();
    m=read();
    build(1,n,1);
    while(m--)
    {
        p=read();
        if(p==1)
        {
            x=read();
            v=read();
            appear(1,n,x,v,1);
            se_add(x,x);
        }
        else if(p==2)
        {
            x=read();
            y=read();
            int num=se_ask(y)-se_ask(x-1);
            if(num)
            {
                node ans=ask(1,n,num,num,1);
                printf("%d",ans.maxx);
                putchar(
);
                last_ans+=ans.maxx;
                change(1,n,num,num,-ans.maxx,1);
                se_add(num,-num);
                continue;
            }
            node ans=ask(1,n,x,y,1);
            change(1,n,ans.max_place,ans.max_place,-ans.maxx,1);
            printf("%d",ans.maxx);
            putchar(
);
            last_ans+=ans.maxx;
        }
        else
        {
            x=read();
            y=read();
            v=read();
            change(1,n,x,y,v,1);
        }
    }
    if(last_ans<10000) printf("QAQ");
    else  if(last_ans<10000000) printf("Sakura");
    else printf("ice");

    return 0;
}
View Code

 

 

 

 

 

 

以上是关于Luogu 45887 全村最好的嘤嘤刀(线段树 树状数组)的主要内容,如果未能解决你的问题,请参考以下文章

luogu3373 模板线段树 2 [线段树]

Luogu 3373 - 模板线段树 2 - [加乘线段树]

Luogu P3372 模板线段树 1

luogu3373线段树2..支持区间加值和乘值的线段树

Luogu5280 [ZJOI2019] 线段树 线段树

[Luogu] 可持久化线段树 1(主席树)