Luogu5889 跳树

原题链接:https://www.luogu.com.cn/problem/P5889

跳树

题目背景

兔子喜欢跳树。

题目描述

一天,兔子在一棵点数为 2 n − 1 2^n-1 2n1 完全二叉树上的一个结点上,他准备进行若干次如下的跳跃。

  • 跳到这个点的左儿子,保证这个点有左儿子。
  • 跳到这个点的右儿子,保证这个点有右儿子。
  • 跳到这个点的父亲,若这个点是根,无视此操作。

其中, i i i 号点要么没有儿子,要么有左儿子 2 × i 2 \times i 2×i 和右儿子 2 × i + 1 2 \times i + 1 2×i+1

兔子会计划性地跳树,他写下了一个长度为 m m m 的序列 o p op op o p op op 中的每个数都是 1 1 1, 2 2 2, 3 3 3 中的一种。操作 i i i 对应从上到下第 i i i 种跳跃方式。

每次,兔子会选择一段区间 [ l , r ] [l,r] [l,r],依次进行跳跃 o p l , o p l + 1 , … , o p r op_l,op_{l+1},\ldots,op_r opl,opl+1,,opr

有时兔子会对一个点的 o p op op 值进行修改。

现在你需要求出兔子每次会跳到哪个结点。

阅读样例解释可以对题意获得更好的理解。

输入格式

第一行三个整数 n , m , q n, m, q n,m,q,表示树的大小的幂次、 o p op op 的长度、操作的次数。

第二行包含 m m m 个整数 o p 1 , 2 , … , m op_{1,2,\ldots,m} op1,2,,m ,表示序列的初值。

接下来 q q q 行,每行一个整数 t y p e type type,若 t y p e type type 1 1 1,接下来三个整数 s , l , r s,l,r s,l,r ,描述起点和进行跳跃的区间;若 t y p e type type 2 2 2 ,接下来两个整数 x , y x,y x,y,描述修改的位置与值。

输出格式

对于每一个 t y p e = 1 type=1 type=1,输出一个数,表示跳跃到的结点。

输入输出样例

输入 #1
3 5 4
1 2 3 3 1
1 3 4 5
1 2 2 4
2 3 1
1 1 2 3
输出 #1
2
1
6

说明/提示

在这里插入图片描述
其中红边为第一次跳跃的路径,蓝边为第二次,绿边为第三次。

所有测试数据的范围和特点如下表所示:

在这里插入图片描述
对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 30 1\leq n \leq 30 1n30 1 ≤ m , q ≤ 5 × 1 0 5 1\leq m,q \leq 5 \times 10^5 1m,q5×105 1 ≤ o p i ≤ 3 1\leq op_i\leq 3 1opi3

题解

思路很简单,三个操作无非是对当前节点编号 v v vv<<1(v<<1)+1v>>1三种操作。如果没有第三种右移操作,则只需要维护左移位数 l m o v lmov lmov和需要出现跳右子树操作后需要加上的 a d d add add,非常容易就可以从左向右进行合并。当加上右移操作后,我们可以发现,若右移操作在前面两种操作之后则可以抵消,但是右移操作却不能被右移之后的其他两种操作抵消,所以操作序列左端的右移操作是没有办法只用 l m o v lmov lmov a d d add add完成维护的,还需要维护右移次数 r m o v rmov rmov。这样,对于单次询问,我们就可以求出一个关于跳树操作的三元组 ( r m o v , l m o v , a d d ) (rmov,lmov,add) (rmov,lmov,add),询问的答案就是((s>>rmov)<<lmov)+add,注意判断右移后是否为 0 0 0

修改因为是单点的,非常简单,不多做赘述(个人认为可以出成区间修改)。

代码

这钵啊,这钵是一道水题调一天系列。

按理来说,由于 n ≤ 30 n\le 30 n30题目又保证跳向子树时子树存在,最终的节点编号一定是 ≤ 2 30 − 1 \le 2^{30}-1 2301的,那么无论是 r m o v , l m o v rmov,lmov rmov,lmov还是 a d d add add都不应该超出 i n t int int的范围,可是在纯用 i n t int int时却出现了溢出的情况,不知是数据有误还是代码中有谬误。

其次就是当我#define int long long之后,只把输出的%d换成%lld,没有改输入,直接 R E \mathcal{RE} RE,后来又只把 a d d add add改成 l o n g   l o n g long\ long long long等等……对拍也没有拍出来,做了大量无用调试,把👴折磨的不轻。在第二次全部 R E \mathcal{RE} RE并查看代码运行后,才发现问题。

G T M D \mathcal{GTMD} GTMD

#include<bits/stdc++.h>
#define ls v<<1
#define rs v<<1|1
#define int long long
using namespace std;
const int M=5e5+5;
struct node{
    int le,ri,rmov,lmov,add;
}tree[M<<2];
int n,m,q,que[M];
node up(node x,node y)
{
    node r;
    r.le=x.le,r.ri=y.ri;
    if(y.rmov>=x.lmov)
    {
        r.rmov=x.rmov+y.rmov-x.lmov;
        r.lmov=y.lmov;
        r.add=y.add;
        return r;        
    }
    r.rmov=x.rmov;
    r.lmov=x.lmov-y.rmov+y.lmov;
    r.add=((x.add>>y.rmov)<<y.lmov)+y.add;
    return r;
}
void build(int v,int le,int ri)
{
    if(le==ri)
    {
        tree[v].le=tree[v].ri=ri;
        if(que[le]==1)tree[v].lmov=1;
        else if(que[le]==2)tree[v].lmov=1,tree[v].add=1;
        else tree[v].rmov=1;
        return;
    }
    int mid=le+ri>>1;
    build(ls,le,mid),build(rs,mid+1,ri);
    tree[v]=up(tree[ls],tree[rs]);
}
void modify(int v,int x,int y)
{
    //printf("%d: %d %d\n",v,tree[v].le,tree[v].ri);
    if(tree[v].le==x&&x==tree[v].ri)
    {
        tree[v].lmov=tree[v].rmov=tree[v].add=0;
        if(y==1)tree[v].lmov=1;
        else if(y==2)tree[v].lmov=1,tree[v].add=1;
        else tree[v].rmov=1;
        return;
    }
    int mid=tree[v].le+tree[v].ri>>1;
    if(x<=mid)modify(ls,x,y);
    else modify(rs,x,y);
    tree[v]=up(tree[ls],tree[rs]);
}
node ask(int v,int le,int ri)
{
    if(le<=tree[v].le&&tree[v].ri<=ri)return tree[v];
    node r;r.lmov=r.rmov=r.add=0;
    int mid=tree[v].le+tree[v].ri>>1;
    if(le<=mid)r=ask(ls,le,ri);
    if(mid<ri)r=up(r,ask(rs,le,ri));
    return r;
}
void in()
{
    scanf("%lld%lld%lld",&n,&m,&q);
    for(int i=1;i<=m;++i)scanf("%lld",&que[i]);
}
void ac()
{
    build(1,1,m);
    node tmp;
    for(int op,a,b;q--;)
    {
        scanf("%lld%lld%lld",&op,&a,&b);
        if(op-1)modify(1,a,b);
        else
        {
            scanf("%lld",&op);
            tmp=ask(1,b,op);
            //printf("rmov:%d lmov:%d add:%d\n",tmp.rmov,tmp.lmov,tmp.add);
            a>>=tmp.rmov;
            if(!a)a=1;
            printf("%lld\n",(a<<tmp.lmov)+tmp.add);
        }
        
    }
}
signed main()
{
    in(),ac();
    //system("pause");
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

ShadyPi

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

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

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

打赏作者

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

抵扣说明:

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

余额充值