洛谷·[TJOI2018]异或

初见安~这里是传送门:洛谷P4592 [TJOI2018] 异或

题解

限制可以理解两层:区间限制,树上的区间和最大异或。很显然建一棵可持久化01trie即可。树上区间的划分可以用到树链剖分重子树优先遍历的顺序。

就没了【?】很裸的一个题,拿来练手还可以。复杂度O(nlog^2n)

还有一种做法是对两个询问开两个可持久化01 trie,对于询问二,01trie以树的形态建立,也就是每个点连自己的父亲;查询的时候求一个lca后u和lca差分,v和lca差分,两个答案取max即可。复杂度是O(nlog)

听说还可以写线段树套线性基,并且支持修改。【太菜了写不动】

下面放O(nlog^2n)的代码。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
#define maxn 100005
using namespace std;
typedef long long ll;
int read() {
	int x = 0, f = 1, ch = getchar();
	while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
	while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
	return x * f;
}

int root[maxn], tot = 0, cnt = 0;
int t[maxn << 5][2], num[maxn << 5], tm[maxn << 5];
int insert(int rt, int x, int k) {
	register int p = ++tot;
	if(k < 0)  {num[p] = x; tm[p] = cnt; return p;}
	register int c = x >> k & 1;
	t[p][c ^ 1] = t[rt][c ^ 1], t[p][c] = insert(t[rt][c], x, k - 1);
	tm[p] = max(tm[t[p][c]], tm[t[p][c ^ 1]]);
	return p;
}

int ask(int p, int lim, int x, int k) {//ask是查询连续区间内的异或最大值。
	if(k < 0) {return num[p];}
	register int c = x >> k & 1;
	if(tm[t[p][c ^ 1]] >= lim) return ask(t[p][c ^ 1], lim, x, k - 1);
	else return ask(t[p][c], lim, x, k - 1);
}

struct edge {int to, nxt;}e[maxn << 1];
int head[maxn], k = 0;
void add(int u, int v) {e[k] = {v, head[u]}; head[u] = k++;}

int n, m, w[maxn];
int dep[maxn], fa[maxn], size[maxn], son[maxn], top[maxn], sub[maxn], dfn[maxn];
void dfs1(int u) {
	size[u] = 1;
	for(int i = head[u], v; ~i; i = e[i].nxt) {
		v = e[i].to; if(v == fa[u]) continue;
		dep[v] = dep[u] + 1, fa[v] = u; 
		dfs1(v); size[u] += size[v];
		if(size[v] > size[son[u]]) son[u] = v;
	}
}

void dfs2(int u, int tp) {
	top[u] = tp; dfn[u] = ++cnt;
	root[cnt] = insert(root[cnt - 1], w[u], 30);
	if(son[u]) dfs2(son[u], tp);
	for(int i = head[u], v; ~i; i = e[i].nxt) {
		v = e[i].to; if(v != fa[u] && v != son[u]) dfs2(v, v);
	}
	sub[u] = cnt;
}

int query(int u, int v, int x) {//树剖跳链
	register int ans = 0, tmp;
	while(top[u] != top[v]) {
		if(dep[top[u]] > dep[top[v]]) swap(u, v);
		tmp = ask(root[dfn[v]], dfn[top[v]], x, 30);
		if(ans < (tmp ^ x)) ans = tmp ^ x;//注意一定是用与x的异或值比较大小
		v = fa[top[v]];
	}
	if(dep[u] > dep[v]) swap(u, v);
	tmp = ask(root[dfn[v]], dfn[u], x, 30);
	if(ans < (tmp ^ x)) return tmp ^ x;
	return ans;
}

signed main() {
	memset(head, -1, sizeof head);
	n = read(), m = read();
	for(int i = 1; i <= n; i++) w[i] = read();
	for(int i = 1, u, v; i < n; i++) u = read(), v = read(), add(u, v), add(v, u);
	dfs1(1); dfs2(1, 1);//树剖预处理
	
	register int op, x, y, z;
	for(int i = 1; i <= m; i++) {
		op = read(), x = read(), y = read();
		if(op == 1) printf("%d\n", y ^ ask(root[sub[x]], dfn[x], y, 30));
		else z = read(), printf("%d\n", query(x, y, z));
	}
	return 0;
}

迎评:)
——End——

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值