鏈接: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;
}