原题链接:https://www.luogu.com.cn/problem/P5889
跳树
题目背景
兔子喜欢跳树。
题目描述
一天,兔子在一棵点数为 2 n − 1 2^n-1 2n−1 完全二叉树上的一个结点上,他准备进行若干次如下的跳跃。
- 跳到这个点的左儿子,保证这个点有左儿子。
- 跳到这个点的右儿子,保证这个点有右儿子。
- 跳到这个点的父亲,若这个点是根,无视此操作。
其中, 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
1≤n≤30,
1
≤
m
,
q
≤
5
×
1
0
5
1\leq m,q \leq 5 \times 10^5
1≤m,q≤5×105 ,
1
≤
o
p
i
≤
3
1\leq op_i\leq 3
1≤opi≤3。
题解
思路很简单,三个操作无非是对当前节点编号
v
v
v做v<<1
、(v<<1)+1
和v>>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 n≤30题目又保证跳向子树时子树存在,最终的节点编号一定是 ≤ 2 30 − 1 \le 2^{30}-1 ≤230−1的,那么无论是 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");
}