幸运数字(nlog^3n 做法)

幸运数字

题目链接:luogu P3292

题目大意

给你一个树,点有点权。
多次询问,每次问你树上的一条路径,它上面所有点任选记得,使得它们点权的异或和最大。

思路

q l o g 2 G qlog^2G qlog2G 做法

看到树上路径——倍增!
看到异或——线性基!

那我们考虑能不能直接暴力,发现线性基可以合并,而且 n l o g 3 n nlog^3n nlog3n 刚好勉勉强强能过。
然后就直接上了。

线性基的合并其实就是将一个线性基中的非 0 0 0 数全部加入另一个线性基中就可以了。
不难看出复杂度是 l o g 2 n log^2n log2n,然后倍增 n l o g n nlogn nlogn,倍增的时候要合并,所以是 n l o g 3 n nlog^3n nlog3n

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long 

using namespace std;

struct node {
	int to, nxt;
}e[40001];
int n, q, fa[20001][16], deg[20001];
int le[20001], KK, x, y;
ll val[20001], d[20001][16][61];

void add(int x, int y) {
	e[++KK] = (node){y, le[x]}; le[x] = KK;
	e[++KK] = (node){x, le[y]}; le[y] = KK;
}

//线性基
void xxj_add(int x, int y, ll now) {
	for (int i = 60; i >= 0; i--)
		if ((now >> i) & 1) {
			if (!d[x][y][i]) {
				d[x][y][i] = now;
				break;
			}
			now ^= d[x][y][i];
		}
}

//线性基合并
void add_xxj(int x, int y, int xx, int yy) {
	for (int i = 60; i >= 0; i--)
		if (d[xx][yy][i])
			xxj_add(x, y, d[xx][yy][i]);
}

void dfs(int now, int father) {
	deg[now] = deg[father] + 1;
	fa[now][0] = father;
	
	for (int i = le[now]; i; i = e[i].nxt)
		if (e[i].to != father) {
			dfs(e[i].to, now);
		}
}

int LCA(int x, int y) {
	if (deg[y] > deg[x]) swap(x, y);
	for (int i = 15; i >= 0; i--)
		if (deg[fa[x][i]] >= deg[y]) {
			add_xxj(0, 0, x, i); 
			x = fa[x][i];
		}
	if (x == y) {
		add_xxj(0, 0, x, 0);//由于是点权,那最后一个点不会被算到,要并进去
		return x;
	}
	for (int i = 15; i >= 0; i--)
		if (fa[x][i] != fa[y][i]) {
			add_xxj(0, 0, x, i);
			add_xxj(0, 0, y, i);
			x = fa[x][i];
			y = fa[y][i];
		}
	add_xxj(0, 0, x, 0);
	add_xxj(0, 0, y, 0);
	add_xxj(0, 0, fa[x][0], 0);//跟上面同理
	return fa[x][0];
}

ll get_max(int x, int y) {
	ll re = 0;
	for (int i = 60; i >= 0; i--)
		if ((re ^ d[x][y][i]) > re)
			re ^= d[x][y][i];
	return re;
}

int main() {
	scanf("%d %d", &n, &q);
	for (int i = 1; i <= n; i++) {
		scanf("%lld", &val[i]);
		xxj_add(i, 0, val[i]);
	}
	
	for (int i = 1; i < n; i++) {
		scanf("%d %d", &x, &y);
		add(x, y);
	}
	
	dfs(1, 0);
	for (int i = 1; i <= 15; i++)
		for (int j = 1; j <= n; j++) {
			fa[j][i] = fa[fa[j][i - 1]][i - 1];
			add_xxj(j, i, j, i - 1);//暴力合并
			add_xxj(j, i, fa[j][i - 1], i - 1);
		}
	
	while (q--) {
		scanf("%d %d", &x, &y);
		memset(d[0][0], 0, sizeof(d[0][0]));
		LCA(x, y);
		printf("%lld\n", get_max(0, 0));
	}
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值