3282: Tree
Time Limit: 30 Sec Memory Limit: 512 MBSubmit: 2500 Solved: 1194
[ Submit][ Status][ Discuss]
Description
给定N个点以及每个点的权值,要你处理接下来的M个操作。
操作有4种。操作从0到3编号。点从1到N编号。
0:后接两个整数(x,y),代表询问从x到y的路径上的点的权值的xor和。
保证x到y是联通的。
1:后接两个整数(x,y),代表连接x到y,若x到Y已经联通则无需连接。
2:后接两个整数(x,y),代表删除边(x,y),不保证边(x,y)存在。
3:后接两个整数(x,y),代表将点X上的权值变成Y。
Input
第1行两个整数,分别为N和M,代表点数和操作数。
第2行到第N+1行,每行一个整数,整数在[1,10^9]内,代表每个点的权值。
第N+2行到第N+M+1行,每行三个整数,分别代表操作类型和操作所需的量。
1<=N,M<=300000
Output
对于每一个0号操作,你须输出X到Y的路径上点权的Xor和。
Sample Input
3 3
1
2
3
1 1 2
0 1 2
0 1 1
1
2
3
1 1 2
0 1 2
0 1 1
Sample Output
3
1
1
HINT
Source
解题思路:时隔半年,再敲动态树,这次更加巩固了。
对于不同的问题,我们只需要修改
pushup函数
Update函数(即Splay树上单点更新)
pushdown函数 即可。
区间查询与区间修改,单点修改看代码即可!这份模板更加清晰易懂!
#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
typedef long long int ll;
const int MAXN = 300095;
int Q[MAXN]; //Splay用栈
int ch[MAXN][2]; //树节点
int fa[MAXN]; //节点的父亲
int sum[MAXN]; //重链和,即维护的路径信息
int A[MAXN]; //每一个节点的值
int rev[MAXN]; //翻转标记,维护Splay用
//判断是否是某个Splay的根节点,旋转用
bool isroot(int rt)
{
return ch[fa[rt]][0] != rt && ch[fa[rt]][1] != rt;
}
bool get(int x) { return ch[fa[x]][1] == x; }
//上传函数,维护节点信息
void pushup(int rt)
{
int l = ch[rt][0];
int r = ch[rt][1];
sum[rt] = (sum[l] ^ sum[r] ^ A[rt]);
}
/*
//给树上节点单点更新,记住这个相当于区间更新,因为树上结点就代表区间信息(Splay树上的节点)
void updateAdd(int rt, int C)
{
if (!rt)
return;
A[rt] = (A[rt] + C) % MOD;
sum[rt] = (sum[rt] + siz[rt] * C % MOD) % MOD;
lazyAdd[rt] = (lazyAdd[rt] + C) % MOD;
}
void updateMul(int rt, int C)
{
if (!rt)
return;
A[rt] = A[rt] * C % MOD;
sum[rt] = sum[rt] * C % MOD;
lazyAdd[rt] = lazyAdd[rt] * C % MOD;
lazyMul[rt] = lazyMul[rt] * C % MOD;
}
*/
//下推函数
void pushdown(int x)
{
if (rev[x])
{
rev[ch[x][0]] ^= 1;
rev[ch[x][1]] ^= 1;
rev[x] ^= 1;
swap(ch[x][0], ch[x][1]);
}
/*
if (lazyAdd[rt])
{
updateAdd(l, lazyAdd[rt]);
updateAdd(r, lazyAdd[rt]);
lazyAdd[rt] = 0;
}
if (lazyMul[rt] != 1)
{
updateAdd(l, lazyMul[rt]);
updateAdd(r, lazyMul[rt]);
lazyMul[rt] = 1;
}
*/
}
//Splay旋转函数,通常无需修改
void Rotate(int x)
{
int old = fa[x], oldf = fa[old], op = get(x);
if (!isroot(old))
ch[oldf][ch[oldf][1] == old] = x;
ch[old][op] = ch[x][op ^ 1];
fa[ch[x][op ^ 1]] = old; //但这里使用isroot,改变之后就不能判断了!
ch[x][op ^ 1] = old;
fa[old] = x;
fa[x] = oldf;
pushup(old);
pushup(x);
}
//Splay函数,将rt变为某棵Splay树的根节点,通常无需修改
void Splay(int x)
{
int tp = 1;
Q[1] = x;
for (int i = x; !isroot(i); i = fa[i])
Q[++tp] = fa[i]; //对于LCT的判断是否是根节点,需要使用isroot,在纯Splay中使用的是fa,不要搞混!
for (int i = tp; i; i--)
pushdown(Q[i]);
for (int FA; !isroot(x); Rotate(x))
{
FA = fa[x];
if (!isroot(FA))
Rotate(get(x) == get(FA) ? FA : x);
}
}
//打通x到整个LCT的根的路径,即fa和ch都是正确的
void Access(int x)
{
int t = 0;
while (x)
{
Splay(x);
ch[x][1] = t;
pushup(x);
t = x;
x = fa[x];
}
}
//把x变为整个LCT的根,先打通路径,然后把他变为他的Splay的根即可。
void Makeroot(int x)
{
Access(x);
Splay(x);
rev[x] ^= 1;
}
//链接函数,先把x变为整个LCT的根(这时x才没有父亲,所以不能用Splay),然后再设置一个父亲即可
void Link(int x, int y)
{
Makeroot(x);
fa[x] = y;
}
//剪断函数,根据Splay原理,可以把x变为y的左节点,然后删除即可
void Cut(int x, int y)
{
Makeroot(x);
Access(y);
Splay(y);
if (ch[y][0] == x)
fa[x] = ch[y][0] = 0;
}
//分割函数,即把x到y这条路径变为一颗Splay树,从而把区间查询变为树上节点查询(Splay原理)
void split(int x, int y)
{
Makeroot(y);
Access(x);
Splay(x);
}
//查询x到y之间是否已经有路径,直接查询所在的LCT的根是否相等即可
int Find(int x)
{
Access(x);
Splay(x);
while (ch[x][0])
x = ch[x][0];
return x;
}
int main()
{
int N, Q;
scanf("%d%d", &N, &Q);
for (int i = 1; i <= N; i++)
{
scanf("%d", &A[i]);
sum[i] = A[i];
}
int x, y;
int op;
while (Q--)
{
scanf("%d", &op);
if (op == 0)
{
scanf("%d%d", &x, &y);
split(x, y); //把x到y这条路径用Splay维护,并且x为根节点(即x包含了整个路径信息)
printf("%d\n", sum[x]);
}
if (op == 1)
{
scanf("%d%d", &x, &y);
if (Find(x) != Find(y))
Link(x, y);
}
if (op == 2)
{
scanf("%d%d", &x, &y);
Cut(x, y);
}
if (op == 3)
{
scanf("%d%d", &x, &y);
Makeroot(x);
A[x] = y;
pushup(x);
}
}
return 0;
}