2019 ACM/ICPC 全国邀请赛(西安)E Tree (树链剖分+线段树)

题意:

给定一棵n个节点的树,每个节点点权为wi。

有三种操作:

操作1,给定s和t,表示将树上(1,s)的简单路径上所有点i的点权wi变为w_{i}|t

操作2,给定s和t,表示将树上(1,s)的简单路径上所有点i的点权wi变为w_{i}\&t

操作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)的,因此本题时间复杂度就是树剖+区间修改线段树的复杂度O(n*logn+m*log^{2}n)

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;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值