E. XOR Tree(树形dp/异或/最近公共祖先)

题目
参考

题意

给定一棵树,树上每个结点取值为 2 0 < = a i < 2 30 2^0<=a_i<2^{30} 20<=ai<230,如果树上存在路径path(u,v)使得,路径上的结点的异或和为0,则这条路径是损坏的。
现能够将树上任一结点的数,修改成其他的数值(可以超出 2 30 2^{30} 230)。
问,最少需要修改多少个结点,才能使树上不存在损坏的路径。

思路

  • 对于损坏的路径,我们只需要把路径上的一个结点,修改成 2 31 2^{31} 231即可,因为树上结点的取值都小于 2 31 2^{31} 231,与之异或,必定非0。
  • 对于损坏的路径,我们要修改离根节点最近的那个点,因为这个结点,影响面最大。修改该结点,可以最大保证破坏更多的损坏路径。
  • 构建以1为根结点root的树
  • 定义dis(u)为结点u与根结点组成的路径的异或和。
  • 根据异或性质,对于任意结点u,v,假设它们的公共祖先为p,则路径(u,v)的异或值,实际等于 d i s ( u ) ⨁ d i s ( v ) ⨁ p dis(u)\bigoplus dis(v)\bigoplus p dis(u)dis(v)p

我们可以预处理,计算每个结点的dis值。dfs遍历每个结点(优先遍历子结点),对于存在损坏的路径,则破坏该路径的公共祖先结点。
在dfs遍历的时候,用set维护每个结点的所有后代结点的dis值。

注意事项

  • 该题卡常,遍历数组时需要遍历小的那个,详见代码33行
  • 需要清空子节点的 set集合,不然内存会爆,详见代码42行

代码

#include<bits/stdc++.h> 
using namespace std;
#define ll long long
const int maxn = 200010;

int n, x, y, ans;
int a[maxn], dis[maxn];
vector<int> g[maxn];
set<int> st[maxn];

// dis[u] = a[u] ^ a[parent(u)] ^ ... ^ a[root]
void cal_pre(int u, int p) {
	dis[u] = a[u];
	if (p != -1) {
		dis[u] ^= dis[p];
	}
	for (auto v: g[u]) {
		if(v == p) {
			continue;
		}
		cal_pre(v, u);
	}
}
void dfs(int u, int p) {
	st[u].insert(dis[u]);
	bool same = false;
	for (auto v: g[u]) {
		if (v == p) {
			continue;
		}
		dfs(v, u);// dfs the son node first.
		// if no this, TLE happen. 
		if (st[v].size() > st[u].size()) {
			st[u].swap(st[v]);
		}
		for (auto x: st[v]) {
			same |= st[u].count(x ^ a[u]);
		}
		for (auto x: st[v]) {
			st[u].insert(x);
		}
		st[v].clear();// if no this, Memory Limit happen.
	}
	if (same) {
		st[u].clear();
		++ans;
	}
}
void solve() {
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) {
		scanf("%d", &a[i]);
		g[i].clear();
		st[i].clear();
	}
	for (int i = 1; i < n; ++i) {
		scanf("%d%d", &x, &y);
		g[x].push_back(y);
		g[y].push_back(x);
	}
	cal_pre(1, -1);
	ans = 0;
	dfs(1, -1);
	printf("%d\n", ans);
}
int main() {
	int t;
//	scanf("%d", &t);
	t = 1;
	while (t--) {
		solve();
	}
}

最后

weixin gongzhonghao搜 对方正在debug,关注下,一起快乐刷题吧

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值