题目描述
异或是一种神奇的运算,大部分人把它总结成不进位加法.
在生活中…xor运算也很常见。比如,对于一个问题的回答,是为1,否为0.那么:
(A是否是男生 )xor( B是否是男生)=A和B是否能够成为情侣
好了,现在我们来制造和处理一些复杂的情况。比如我们将给出一颗树,它很高兴自己有N个结点。树的每条边上有一个权值。我们要进行M次询问,对于每次询问,我们想知道某两点之间的路径上所有边权的异或值。
输入输出格式
输入格式:输入文件第一行包含一个整数N,表示这颗开心的树拥有的结点数,以下有N-1行,描述这些边,每行有3个数,u,v,w,表示u和v之间有一条权值为w的边。接下来一行有一个整数M,表示询问数。之后的M行,每行两个数u,v,表示询问这两个点之间的路径上的权值异或值。
输出格式:输出M行,每行一个整数,表示异或值
输入输出样例
说明
对于40%的数据,有1 ≤ N,M ≤ 3000;
对于100%的数据,有1 ≤ N ,M≤ 100000。
这道题看着很可怕,又是异或又是树什么的。鉴于异或的特殊性,我google了下异或是否有逆运算,了解了这些公式:
a ^ 0 = a
a ^ a = 0
a ^ b = c -> a = b ^ c -> b = a ^ c
于是我就想,如果我们求的图形不是树,而是简单的链状结构,那么结合上面的公式,岂不是可以很简单地用前缀异或与差分的思想来解决?
但这是树啊,于是我另辟蹊径。
也许是树链剖分?也许要用LCA?然而我都不会。
但是!解题的关键恰恰是看似把题目复杂化了的xor
运算!此题的最简单解法就是用前缀异或与差分的思想
异或运算xor
满足交换律和结合律。
以任意结点为根节点,从该节点遍历其他结点,算出路径上边权的异或值。那么考虑这种一般情况,结点4和结点5有最近公共祖先(LCA)结点3,那么根节点1到4的异或值为X1,4, 1到5的异或值为X1,5, 1到3的异或值为X1,3,我们要求的是从结点4到5的异或值。
有如下几个式子:
X1,3 ^ X3,4 = X1,4 -> X3,4 = X1,3 ^ X1,4
X1,3 ^ X3,5 = X1,5 -> X3,5 = X1,3 ^ X1,5
X4,5 = X3,4 ^ X3,5 = X1,3 ^ X1,4 ^ X1,3 ^ X1,5 = X1,4 ^ X1,5
于是,要求出从结点4到结点5的异或值,只要异或从根结点到结点4和结点5的异或值。
#include <iostream>
#include <vector>
using namespace std;
struct Node {
int to, cost;
int xo;
Node (int t, int c): to(t), cost(c) {}
};
const int maxn = 100005;
int n, m;
vector<Node> G[maxn];
int xo[maxn] = {};
bool vis[maxn] = {};
void dfs(int u)
{
for (size_t i = 0; i < G[u].size(); ++i) {
int to = G[u][i].to, c = G[u][i].cost;
if (!vis[to]) {
xo[to] = xo[u] ^ c;
vis[to] = true;
dfs(to);
}
}
}
int main()
{
cin >> n;
for (int i = 1; i < n; ++i) {
int u, v, c;
cin >> u >> v >> c;
G[u].push_back(Node(v, c));
G[v].push_back(Node(u, c));
}
vis[1] = true;
dfs(1);
cin >> m;
for (int i = 1; i <= m; ++i) {
int x, y;
cin >> x >> y;
cout << (xo[x] ^ xo[y]) << endl;
}
}