TJOI2018 d2t2 异或

题目描述

现在有一颗以 1 为根节点的由 n 个节点组成的树,树上每个节点上都有一个权值 vi 。现在有 Q 次操作,操作如下:

  • y :查询节点x 的子树中与y 异或结果的最大值
  • z :查询路径x 到y 上点与z 异或结果最大值


对于异或最大值问题,考虑使用字典树解决。

写了一个非正解的方法,比较愚蠢,线段树套字典树+树链剖分。最早我听说这个方法是错的,十分不解。写完后我才知道问题所在。



呵,出题人你就不能给个512MB吗???(而且这样做,时间复杂度也挺大,线段树套trie的运算量可不是吃素的)

#include <cstdio>
#define N 100010
inline char gc() {
	static char now[1<<16], *S, *T;
	if(S == T) {T = (S = now) + fread(now, 1, 1<<16, stdin); if(S == T) return EOF;}
	return *S++;
}
inline int read() {
	int x = 0; char c = gc();
	while(c < '0' || c > '9') c = gc();
	while(c >= '0' && c <= '9') {x = x * 10 + c - 48; c = gc();}
	return x;
}
inline void print(int x) {
	if(!x) {puts("0"); return ;}
	int dgt[20], now = 0;
	while(x) {dgt[++now] = x % 10; x/= 10;}
	for(; now; --now) putchar('0' + dgt[now]); puts("");
}
struct edge {int to, next;}e[N<<1];
int val[N], head[N], fa[N], dep[N], sz[N], son[N], st[N], ed[N], rid[N], tp[N];
int n, q, cnt = 1, dfn = 0, ans;
inline void ins(int x, int y) {e[++cnt].to = y; e[cnt].next = head[x]; head[x] = cnt;}
void dfs1(int x, int f, int d) {
	fa[x] = f; dep[x] = d; sz[x] = 1; son[x] = 0;
	for(int i = head[x]; i; i = e[i].next) {
		int y = e[i].to;
		if(y != f) {
			dfs1(y, x, d + 1); sz[x]+= sz[y];
			if(!son[x] || sz[y] > sz[son[x]]) son[x] = y;
		}
	}
}
void dfs2(int x, int f) {
	st[x] = ed[x] = ++dfn; rid[dfn] = x; tp[x] = f;
	if(!son[x]) return ;
	dfs2(son[x], f);
	for(int i = head[x]; i; i = e[i].next) {
		int y = e[i].to;
		if(y != fa[x] && y != son[x]) dfs2(y, y);
	}ed[x] = dfn;
}
struct node {int next[2];}trie[N*500]; int len = N<<2;
inline void add(int p, int x) {
	for(int i = 30; i >= 0; --i) {
		int now = (x & (1<<i))?1:0;
		if(trie[p].next[now]) p = trie[p].next[now];
		else p = (trie[p].next[now] = ++len);
	}
}
inline int mymax(int x, int y) {return (x>y)?x:y;}
inline void query(int p, int x) {
	int now = 0;
	for(int i = 30; i >= 0; --i) {
		int need = (x & (1<<i))?0:1;
		if(trie[p].next[need]) p = trie[p].next[need], now+= (1<<i);
		else p = trie[p].next[1 - need];
	}
	ans = mymax(ans, now);
}
void build(int p, int l, int r) {
	if(l == r) {add(p, val[rid[l]]); return ;}
	int mid = (l + r)>>1;
	build(p<<1, l, mid); build(p<<1|1, mid + 1, r);
	for(int i = l; i <= r; ++i) add(p, val[rid[i]]);
}
void getans(int p, int l, int r, int x, int y, int z) {
	if(x <= l && r <= y) {query(p, z); return ;}
	int mid = (l + r)>>1;
	if(x <= mid) getans(p<<1, l, mid, x, y, z);
	if(mid + 1 <= y) getans(p<<1|1, mid + 1, r, x, y, z);
}
inline int findans(int x, int y, int z) {
	int fx = tp[x], fy = tp[y];
	while(fx != fy) {
		if(dep[fx] < dep[fy]) {int temp = fx; fx = fy; fy = temp; temp = x; x = y; y = temp;}
		getans(1, 1, n, st[fx], st[x], z);
		x = fa[fx]; fx = tp[x];
	}
	(dep[x] > dep[y])?getans(1, 1, n, st[y], st[x], z):getans(1, 1, n, st[x], st[y], z);
}
int main() {
	n = read(); q = read();
	for(int i = 1; i <= n; ++i) val[i] = read();
	for(int i = 1; i < n; ++i) {int x = read(), y = read(); ins(x, y); ins(y, x);}
	dfs1(1, 0, 1); dfs2(1, 1); build(1, 1, n);
	for(int i = 1; i <= n; ++i) {
		int opt = read(), x = read(), y = read();
		ans = 0;
		if(opt == 1) {
			getans(1, 1, n, st[x], ed[x], y);
			print(ans);
		}else {
			int z = read();
			findans(x, y, z);
			print(ans);
		}
	}
	return 0;
}

非要逼着写正解。

对于子树,就使用dfs序转为序列可持久化字典树求解。对于路径,拆成lca引导的两条路径,使用可持久化字典树进行维护。

至今不知道大佬们怎么做到只维护一棵字典树。

#include <cstdio>
#define N 100010
inline char gc() {
	static char now[1<<16], *S, *T;
	if(S == T) {T = (S = now) + fread(now, 1, 1<<16, stdin); if(S == T) return EOF;}
	return *S++;
}
inline int read() {
	int x = 0; char c = gc();
	while(c < '0' || c > '9') c = gc();
	while(c >= '0' && c <= '9') {x = x * 10 + c - 48; c = gc();}
	return x;
}
inline void print(int x) {
	if(!x) {puts("0"); return ;}
	int dgt[20], now = 0;
	while(x) {dgt[++now] = x % 10; x/= 10;}
	for(; now; --now) putchar('0' + dgt[now]); puts("");
}
struct edge {int to, next;}e[N<<1];
int val[N], head[N], st[N], ed[N], rid[N];
int n, q, cnt = 1, dfn = 0;
inline void ins(int x, int y) {e[++cnt].to = y; e[cnt].next = head[x]; head[x] = cnt;}
inline int mymax(int x, int y) {return (x > y)?x:y;}
int ans, each;
int root1[N], L1[N*50], R1[N*50], v1[N*50], len1 = 0;
void add1(int pre, int &now, int key, int dgt) {
	now = ++len1; v1[now] = v1[pre] + 1;
	if(dgt < 0) return ; int x = key & (1<<dgt);
	if(!x) {R1[now] = R1[pre]; add1(L1[pre], L1[now], key, dgt - 1);}
	else {L1[now] = L1[pre]; add1(R1[pre], R1[now], key, dgt - 1);}
}
void query1(int pre, int now, int key, int dgt) {
	if(dgt < 0) {ans = mymax(ans, each); return ;}
	int x = key & (1<<dgt), k;
	if(x) {
		k = v1[L1[now]] - v1[L1[pre]];
		if(k) each|= 1<<dgt, query1(L1[pre], L1[now], key, dgt - 1);
		else query1(R1[pre], R1[now], key, dgt - 1);
	}else {
		k = v1[R1[now]] - v1[R1[pre]];
		if(k) each|= 1<<dgt, query1(R1[pre], R1[now], key, dgt - 1);
		else query1(L1[pre], L1[now], key, dgt - 1);
	}
}
int root2[N], L2[N*50], R2[N*50], v2[N*50], len2 = 0;
void add2(int pre, int &now, int key, int dgt) {
	now = ++len2; v2[now] = v2[pre] + 1;
	if(dgt < 0) return ; int x = key & (1<<dgt);
	if(!x) {R2[now] = R2[pre]; add2(L2[pre], L2[now], key, dgt - 1);}
	else {L2[now] = L2[pre]; add2(R2[pre], R2[now], key, dgt - 1);}
}
void query2(int pre, int now, int key, int dgt) {
	if(dgt < 0) {ans = mymax(ans, each); return ;}
	int x = key & (1<<dgt), k;
	if(x) {
		k = v2[L2[now]] - v2[L2[pre]];
		if(k) each|= 1<<dgt, query2(L2[pre], L2[now], key, dgt - 1);
		else query2(R2[pre], R2[now], key, dgt - 1);
	}else {
		k = v2[R2[now]] - v2[R2[pre]];
		if(k) each|= 1<<dgt, query2(R2[pre], R2[now], key, dgt - 1);
		else query2(L2[pre], L2[now], key, dgt - 1);
	}
}
int f[N][17], dep[N];
void dfs(int x, int fa) {
	f[x][0] = fa; st[x] = ++dfn; rid[dfn] = x; add2(root2[fa], root2[x], val[x], 30);
	for(int i = head[x]; i; i = e[i].next) {
		int y = e[i].to;
		if(y != fa) {dep[y] = dep[x] + 1; dfs(y, x);}
	}ed[x] = dfn;
}
inline int lca(int x, int y) {
	if(dep[x] > dep[y]) {int temp = x; x = y; y = temp;}
	for(int i = 16; i >= 0; --i) if(dep[y] - dep[x] >= (1<<i)) y = f[y][i];
	if(x == y) return x;
	for(int i = 16; i >= 0; --i)
		if(f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
	return f[x][0];
}
int main() {
	n = read(); q = read();
	for(int i = 1; i <= n; ++i) val[i] = read();
	for(int i = 1; i < n; ++i) {int x = read(), y = read(); ins(x, y); ins(y, x);}
	dep[1] = 1; dfs(1, 0);
	for(int j = 1; j < 17; ++j)
		for(int i = 1; i <= n; ++i) f[i][j] = f[f[i][j - 1]][j - 1];
	for(int i = 1; i <= n; ++i) add1(root1[i - 1], root1[i], val[rid[i]], 30);
	for(int i = 1; i <= q; ++i) {
		int opt = read(), x = read(), y = read(); ans = each = 0;
		if(opt == 1) {query1(root1[st[x] - 1], root1[ed[x]], y, 30); print(ans);}
		else {
			int z = read(), w = lca(x, y); int pre = f[w][0];
			query2(root2[pre], root2[x], z, 30); each = 0;
			query2(root2[pre], root2[y], z, 30); print(ans);
		}
	}
	return 0;
}

呼,现在就舒坦多了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值