2021牛客暑期多校训练营4 E. Tree Xor(线段树/Trie)超详细题解
链接:https://ac.nowcoder.com/acm/contest/11255/E
来源:牛客网
题目描述
Bob has a tree with nn nodes and the weight of i-th node is wiwi.
But Bob forgot w1...nw1...n, he only remembers wiwi is an integer in [li,ri][li,ri] and wu xor wvwu xor wv for each edge (u,v)(u,v) in the tree.
Now Bob wants to know the number of possible values of w1...nw1...n.
XOR means bitwise exclusive OR
输入描述:
The first line has one integers nn.
Then there are nn lines, the i-th line has two integers li,rili,ri.
Then there are n−1n−1 lines, each line has three integers u,v,wu xor wvu,v,wu xor wv denote the infomation for each edge.
1≤n≤1051≤n≤105
0≤li≤ri<2300≤li≤ri<230
0≤wu xor wv<2300≤wu xor wv<230
输出描述:
Output the answer.
示例1
输入
复制
4
0 7
1 6
2 5
3 4
1 2 0
1 3 7
2 4 6
输出
复制
2
学了题解我大受震撼...
设连接\(u, v\)两点的边的边权为\(w\),那么不难发现若\(u\)的权值为0的话那么\(v\)的权值就是\(w\);同样如果\(u\)的权值唯一确定那么\(v\)的权值也就确定了。不妨设1号点为根节点且其权值初始化为0,那么从根节点对整棵树进行一次DFS就可以得到每个节点的初始值\(w_i\)。然后考虑令根节点权值变为\(a\),那么每个节点的权值就为\(w_i\bigoplus a\)。因此问题就变成了在满足\(\forall i\in [1,n],l_i\leq w_i \bigoplus a \leq r_i\)条件下的a的个数。朴素的想法是对于每个区间\([l_i, r_i]\)的每个数与\(w_i\)进行异或,得到一些新的区间(注意不一定连续!),对这些区间进行区间+1操作(可以用差分实现),最终被覆盖n次的点的数目就是答案了。但是因为区间异或得到的区间不一定连续,因此暴力就完蛋了。一种思路是按照01Trie递归查询的方式求出来\(a\bigoplus w_i \leq l_i,\ a\bigoplus w_i \leq r_i\)的\(a\)的范围然后进行差分。具体可以参考这位大佬的博客:https://blog.csdn.net/hddddh/article/details/119133637?ops_request_misc=&request_id=&biz_id=&utm_medium=distribute.pc_search_result.none-task-blog-2alles_rank~default-2-119133637.pc_search_all_es&utm_term=2021牛客多校+tree+xor&spm=1018.2226.3001.4187
01Trie递归的写法可以参照我之前写的HDU6955的题解:https://www.cnblogs.com/lipoicyclic/p/15040070.html
另一种思路就是题解的思路了,考虑在题目给的数据范围\([0,2^{30}-1]\)上建线段树,线段树的每个节点的覆盖范围都是\([x...x000...000,x...x111...111]\)的形式,即左右端点的前k位相同(任意),后面是000...000到111...111(可以手动验证一下)。这样就很契合这个题了。为什么这么说呢?因为这样的节点对应的区间与\(w_i\)进行区间异或操作得到的正好是连续的区间!证明很简单:设线段树上某个节点对应的区间的左端点为\(l\),右端点为\(r\),那么\(l\)到\(r\)这段数的前k位与\(w_i\)异或得到的结果显然相同,因此只需要证明\([0, 111...111]\)与\(w_i\)的后面若干位异或后还能得到\([0, 111...111]\)。可以把与\(w_i\)进行异或运算看做是函数,由于异或运算的性质,容易知道这个函数是单射也是满射,因此是一一映射,得证。因此可以采用线段树的写法,首先遍历1到\(n\),对于每个区间\([l_i, r_i]\),执行类似线段树的query操作,对于能被\([l_i,r_i]\)覆盖到的线段树的节点对应的区间算出其与\(w_i\)异或得到的新区间(由上述证明,这一定是连续区间),将这个区间执行区间+1操作。因为本题中节点权值数据范围实在太大,没用常规差分操作实现区间加法,只能把要+1和-1的位置用结构体存储并排序,最后计算的时候遍历结构体更新答案。
#include <bits/stdc++.h>
using namespace std;
#define N 100005
#define M 100005
int n, l[100005], r[100005];
int head[N], ver[2 * M], Next[2 * M], edge[2 * M], tot = 0;
int w[N];
struct segment {
int pos, add;
};
bool cmp(segment a, segment b) {
if(a.pos != b.pos) return a.pos < b.pos;
else return a.add < b.add;
}
void add(int x, int y, int z) {
ver[++tot] = y, edge[tot] = z, Next[tot] = head[x], head[x] = tot;
}
void dfs(int x, int pre, int val) {
w[x] = val;
for(int i = head[x]; i; i = Next[i]) {
int y = ver[i], z = edge[i];
if(y == pre) continue;
dfs(y, x, val ^ z);
}
}
vector<segment> v;
void generateSegment(int l, int r, int ww) {//将区间[l, r]与w异或得到的区间+1,转化为差分的点+-
segment seg1, seg2;
int len = r - l + 1;
seg1.pos = (l ^ (ww & (~(len - 1))));
seg2.pos = (l ^ (ww & (~(len - 1)))) + len;
seg1.add = 1, seg2.add = -1;
v.push_back(seg1);
v.push_back(seg2);
return;
}
void dfs1(int l, int r, int x, int y, int ww) {
if(x >= l && y <= r) {
generateSegment(x, y, ww);//被覆盖
return;
}
int mid = (x + y) >> 1;
if(l <= mid) dfs1(l, r, x, mid, ww);
if(r > mid) dfs1(l, r, mid + 1, y, ww);
return;
}
int main() {
ios::sync_with_stdio(false);
cin >> n;
for(int i = 1; i <= n; i++) {
cin >> l[i] >> r[i];
}
for(int i = 1; i <= n - 1; i++) {
int u, v, w;
cin >> u >> v >> w;
add(u, v, w);
add(v, u, w);
}
dfs(1, 0, 0);//获得初始权重
for(int i = 1; i <= n; i++) {
dfs1(l[i], r[i], 0, (1 << 30) - 1, w[i]);//对0到(1 << 30) - 1这段区间进行模拟线段树操作
}
int ans = 0, sum = 0;//sum为差分数组的前缀和
sort(v.begin(), v.end(), cmp);//对差分用的结构体排序
for(int i = 0; i < v.size() - 1; i++) {
sum += v[i].add;
if(sum == n) ans += v[i + 1].pos - v[i].pos;//这个点到下一个点前面的这段区间被覆盖了n次,说明同时满足了n个异或不等式
}
cout << ans;
return 0;
}