题意:
给定一棵n个节点的树,每个节点点权为wi。
有三种操作:
操作1,给定s和t,表示将树上(1,s)的简单路径上所有点i的点权wi变为。
操作2,给定s和t,表示将树上(1,s)的简单路径上所有点i的点权wi变为。
操作3,给定s和t,表示将树上(1,s)的简单路径上每个点i视为一堆石子,点i的点权wi视为该堆石子有wi颗,再加上一堆含有t颗石子的石堆,判断Nim游戏中该局面先手是否必胜(即所有堆石子数的异或和是否为0,为0则必败,否则必胜),必胜输出YES,必败输出NO。
题解:
理解一下以上三种操作,假设以1为根,前两种操作相当于树链上的区间修改,操作3相当于树链上的查询——查询该条链上的区间异或和是否为t。
我们先不看位运算(虽然事实上位运算标记的更新才是本题的难点),先把区间修改当成普通的加减,区间查询当成普通的区间求和。
首先树链剖分+带标记的线段树应该是必不可少的。
树链剖分+带标记的线段树可以解决树链上的区间修改和区间查询问题,不过本题不要求你会树链剖分(我就不会QAQ),但是应该要会线段树的标记下传和处理多标记之间的影响。
位运算变成加减后就是树链剖分的模板题了,可以先去随便找个洛谷P3384 【模板】树链剖分板子。
然后回到本题,开始考虑标记之间的影响。(昨晚躺在床上纯脑子YY,从凌晨一点多想到三点= =)
首先考虑标记对区间异或和的更新。
对于与标记,YY一会后可以发现:
如果 与标记 某二进制位上为0,则和 区间异或和 相与 后该二进制位也为0,等价于 与标记 先和 区间的每个数的该二进制位 相与,再对该区间的该二进制位异或(该位会为0);
如果 与标记 某二进制位上为1,相与后不影响该二进制位上的区间异或值,该是啥还是啥,也不影响区间内每个数 该二进制位 的值。
即 与标记 和 区间异或和 相与,等价于 与标记 先和 区间每个值相与, 再计算区间异或和,所以 与标记 的区间更新就是直接和 区间异或和 相与。
对于或标记,再YY一会儿可以发现:
若 或标记 某二进制位上为0,则 相或 后对原数的该二进制位没有影响;
若 或标记 某二进制位上为1,则 区间所有数的该二进制 都变为1,则该位区间异或和取决于区间长度的奇偶。
所以接下来考虑区间长度的奇偶。
若区间长度为奇数,则 或标记二进制位上 为1的位,区间异或和该位上也为1,或标记二进制位上 为0 的位保持不变,那么区间更新其实就是 或标记 和 区间异或和 直接相或即可。
若区间长度为偶数,则 或标记二进制位上 为1的位,区间异或和上该位为0,或标记二进制位上为0 的位保持不变,想到把或标记取反,则区间更新等价于 取反后的或标记 和 区间异或和 直接相与。
下面考虑下传过程中标记的更新。
对于多个与标记之间,因为区间更新都是直接相与,而与运算具有结合律,因此多个与标记可以直接相与。
对于多个或标记之间,我考虑了:对于此后的奇数部分,因为多个或标记都是直接或,或运算具有结合律,多个标记可以直接相或;对于此后的偶数部分,多个或标记都分别取反再一个个相与,可以知道和 先多个标记相或 再 对整个或标记取反,再是等价的。(事实上昨晚我并不确定有没有结合律之类的结论,都是自己脑中分情况讨论验证的,这个过程描述起来很麻烦。。。)
总结就是,与标记下传 可以直接和 子节点的与标记 相与,或标记下传 可以直接与 子节点的或标记 相或。
最后考虑下传过程中一种标记对另一种标记的更新。
这里我们先下传与标记,再下传或标记。
如果下传的 与标记 不更新 或标记,那么相当于原来在 或标记后面 出现的 与标记对或标记的影响消失了,反而先出现的或标记会对后出现的与标记造成影响了。
与标记 更新 或标记,直接将 或标记 与上 与标记即可。(这里考虑的要点就是,不同标记到来的先后顺序对区间产生影响是不同的,如何统一的通过标记来下传这种影响,使得可以不用考虑标记下传的先后顺序)。
下传标记是O(1)的,因此本题时间复杂度就是树剖+区间修改线段树的复杂度
YY完了以后,只需要拿着树链剖分模板改一改,重点是pushdown部分,然后就可以AC本题了。
(PS:我只改了别人模板代码的输入部分,线段树部分,然后在path_add函数中添加了参数op,在path_query中将求和改为求异或和,树剖部分一点没动,因此我之前说做本题不需要掌握树剖,只需要知道能干啥即可, 重点在于线段树中位运算标记的考虑)
AC代码:
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#define MAXN 100005
#define MAXM 200005
using namespace std;
inline void getint(int &num){
char ch;
while(!isdigit(ch = getchar()));
num = ch - '0';
while(isdigit(ch = getchar())) num = num * 10 + ch - '0';
}
int N, M, R, P, w[MAXN];
int sum[MAXN << 2], orf[MAXN << 2],andf[MAXN<<2];
#define lc (u << 1)
#define rc (u << 1 | 1)
int dep[MAXN], fa[MAXN], sz[MAXN], hson[MAXN], tope = 0;
int dfn[MAXN], dfstime = 0, top[MAXN], id[MAXN];
inline void pushup(int u) {sum[u] = (sum[lc] ^ sum[rc]) ;}
inline void pushdown(int u, int l, int r){
if(andf[u]!=-1){
sum[lc] &= andf[u], andf[lc] &= andf[u],orf[lc] &= andf[u];
sum[rc] &= andf[u], andf[rc] &= andf[u],orf[rc] &= andf[u];
andf[u]=-1;
}
if(orf[u]!=0){
int mid = l + r >> 1;
if((mid-l+1)&1) sum[lc] |= orf[u],orf[lc] |= orf[u];
else sum[lc] &= (~orf[u]),orf[lc] |= orf[u];
if((r-mid)&1) sum[rc] |= orf[u],orf[rc] |= orf[u];
else sum[rc] &= (~orf[u]),orf[rc] |= orf[u];
orf[u]=0;
}
}
inline void build(int u, int l, int r){//与标记初始化为-1,或标记初始化为0,可以保证不影响结果
orf[u]=0;andf[u]=-1;
if(l == r) {sum[u] = w[id[l]] ; return;}
int mid = l + r >> 1;
if(l <= mid) build(lc, l, mid);
if(mid < r) build(rc, mid + 1, r);
pushup(u);
}
inline void add(int u, int l, int r, int al, int ar, int v,int op){//op 0:and 1:or
if(l == al && r == ar) {
if(op==0) sum[u] &= v,andf[u] &= v, orf[u] &= v;
else if((r-l+1)&1) sum[u] |= v, orf[u] |= v;//,andf[u] |= v; 或标记不需要修改与标记,修改了也没事
else sum[u] &= (~v), orf[u] |= v;//,andf[u] &= (~v);
return;
}
pushdown(u, l, r);
int mid = l + r >> 1;
if(ar <= mid) add(lc, l, mid, al, ar, v,op);
else if(al > mid) add(rc, mid + 1, r, al, ar, v,op);
else add(lc, l, mid, al, mid, v,op), add(rc, mid + 1, r, mid + 1, ar, v,op);
pushup(u);
}
inline int query(int u, int l, int r, int ql, int qr){
if(l == ql && r == qr) return sum[u];
pushdown(u, l, r);
int mid = l + r >> 1;
if(qr <= mid) return query(lc, l, mid, ql, qr);
else if(ql > mid) return query(rc, mid + 1, r, ql, qr);
else return (query(lc, l, mid, ql, mid) ^ query(rc, mid + 1, r, mid + 1, qr));
}
struct Edge{
int np;
Edge *nxt;
} *V[MAXN], E[MAXM];
inline void addedge(int u, int v) {E[++tope].np = v, E[tope].nxt = V[u], V[u] = &E[tope];}
inline void dfs1(int u, int f, int d){
dep[u] = d, fa[u] = f, sz[u] = 1, hson[u] = 0;
for(register Edge *ne = V[u]; ne; ne = ne->nxt){
if(ne->np == f) continue;
dfs1(ne->np, u, d + 1);
sz[u] += sz[ne->np];
if(sz[ne->np] > sz[hson[u]]) hson[u] = ne->np;
}
} // 第一次 dfs: 预处理深度 dep, 父节点 fa, 子树大小 sz 和重儿子 hson
inline void dfs2(int u, int tp){
dfn[u] = ++dfstime, id[dfstime] = u, top[u] = tp;
if(!hson[u]) return;
dfs2(hson[u], tp); // 先 dfs 重儿子, 保证重链的 dfs 序连续
for(register Edge *ne = V[u]; ne; ne = ne->nxt)
if(ne->np != fa[u] && ne->np != hson[u]) dfs2(ne->np, ne->np);
} // 第二次 dfs: 预处理 dfs 序 dfn, 线段树上对应位置 id, 所在重链链头 top
inline void path_add(int u, int v, int w,int op){
while(top[u] != top[v]){
if(dep[top[u]] < dep[top[v]]) swap(u, v);
add(1, 1, N, dfn[top[u]], dfn[u], w,op);
u = fa[top[u]];
}
if(dep[u] > dep[v]) swap(u, v);
add(1, 1, N, dfn[u], dfn[v], w,op);
}
inline int path_query(int u, int v){
int res = 0;
while(top[u] != top[v]){
if(dep[top[u]] < dep[top[v]]) swap(u, v);
res ^= query(1, 1, N, dfn[top[u]], dfn[u]);
u = fa[top[u]];
}
if(dep[u] > dep[v]) swap(u, v);
res ^= query(1, 1, N, dfn[u], dfn[v]);
return res;
}
int main(){
getint(N), getint(M),R=1;//R根节点序号 ,N节点数,M查询数
for(register int i = 1; i <= N; i++) getint(w[i]);//w[i]权值
for(register int i = 1; i < N; i++){//建树
int u, v; getint(u), getint(v);
addedge(u, v), addedge(v, u);
}
dfs1(R, 0, 1), dfs2(R, R);//树链剖分
build(1, 1, N);//建线段树
int x=1;//操作的两点中其中一点始终为1
while(M--){
int opt, y, z;
getint(opt),getint(y),getint(z);//操作种类和s、t
if(opt==1) path_add(x, y, z,1);//或操作
else if(opt ==2) path_add(x, y, z,0);//与操作
else if(opt == 3){//查询
cout<<((path_query(x,y)^z)==0?"NO\n":"YES\n");
}
}
return 0;
}