2021牛客暑期多校训练营4 E.Tree Xor (二进制,线段树)
-
题意:有一颗\(n\)个结点的树,每个点都有点权\(w[i]\),但现在并不知道点权是多少,对于条边\((u,v)\),我们知道\(w[u]\ xor\ w[v]\)的值,以及每个点权的范围\(l[i],r[i]\).
-
题解:先假设\(w'[1]=0\),然后可以线性推出其他\(w'[i]\)的值,在推的过程中不难发现,假如\(w[1]=a\),那么其他\(w[i]\)均要\(xor\)一个\(a\)的值,那么假设真点权\(w[1]=a\),可得\(w[1,2,...,n]=w'[1,2,....,n]\ xor \ a\).即\(w[i]=w'[i]\ xor \ a\).
而\(l[i]\le w[i]\le r[i]\),所以\(l[i]\le w'[i]\ xor\ a\le r[i]\).\(w'[i]\)的值是已知不变的,那么问题也就转化成了求合法的\(a\)的范围.
即\(l[i]\ xor \ w'[i]\le a\le r[i]\ xor \ w'[i]\),也就是求\([l_i,r_i] \ xor\ w'[i]\)后的个数,然后\(n\)个集合取交就是答案。
但是这个\([l_i,r_i] \ xor\ w'[i]\)并不好求,因为\(xor\)后可能并不是连续的,而是一段一段的,接下来就是这道题目的关键点了,也就是出题人的神仙求法.
我们建一棵\([0,2^{30}-1]\)的权值线段树,然后每个区间用二进制表示出来
那么第一层就是\([000...000,111...111]\),第二层为\([0\ 0...000,0\ 1...111]\)和\([1\ 0...000,1\ 1...111]\),第三层为\([00\ 0...000,00\ 1...111]\),\([01\ 0...000,01\ 1...111]\),\([10\ 0...000,10\ 1...111]\),\([11\ 0...000,11\ 1...111]\).
到这就不难不发现,我们所建的线段树的每个区间,都是高\(k\)位相同,低\(30-k\)位都是\(0...000\)和\(1...111\).那么对于这样的区间比如说\([xy\ 0...000,xy\ 1...111]\),我们随便\(xor\)一个数,那么这个数的高\(2\)位和这个区间的左右端点\(xor\)后得到一个新的值\(x'y'\),而这个数剩下的低\(28\)位和\([0...000,1...111]\)这个范围里的每一个数\(xor\)后,得到的所有数一定还是\([0...000,1...111]\).那么我们的\([xy\ 0...000,xy\ 1...111]\)\(xor\)完某个数后就会得到一个连续的新区间\([x'y'\ 0...000,x'y'\ 1...111]\).
也就是说我们把区间\([l_i,r_i]\)分成了若干个子区间,在我们建立好的线段树上去匹配它们,因为上述性质,匹配的子区间\(xor\)任意数得到的一定是一个长度相同的其他子区间,我们记录\(xor\)后的区间.
那么取这\(n\)个集合的交集怎么求呢?这里考虑因为每个集合\(s_i\)的区间都是连续的,所以说\(s_i\)的所有区间都是不会相交的,那么我们直接对所有区间左端点记录1右端点记录-1,然后按左端点大小排序维护前缀和,当前缀和为\(n\)时,说明已经有\(n\)个区间相交了,因为单独某个集合的区间都不会相交,所以说明此时的下一个肯定是右端点,直接贡献给答案即可. 这个其实在纸上画一画还是很好理解的,感觉可以当板子来记.
-
代码:
#include <iostream> #include <iomanip> #include <cstdio> #include <cstring> #include <cmath> #include <algorithm> #include <stack> #include <queue> #include <vector> #include <map> #include <set> #include <unordered_set> #include <unordered_map> #define ll long long #define db double #define fi first #define se second #define pb push_back #define me memset #define rep(a,b,c) for(int a=b;a<=c;++a) #define per(a,b,c) for(int a=b;a>=c;--a) const int N = 1e6 + 10; const int mod = 1e9 + 7; const int INF = 0x3f3f3f3f; using namespace std; typedef pair<int,int> PII; typedef pair<ll,ll> PLL; int gcd(int a,int b){return b?gcd(b,a%b):a;} int lcm(int a,int b){return a/gcd(a,b)*b;} int n; int l[N],r[N]; int w[N]; vector<PII> edge[N]; vector<PII> itv; void dfs(int u,int fa){ for(auto to:edge[u]){ if(to.fi==fa) continue; w[to.fi]=to.se^w[u]; dfs(to.fi,u); } } void modify(int L,int R,int l,int r,int bit,int x){ if(L>=l && R<=r){ int curl=L&(((1<<30)-1)^((1<<bit)-1)); int curx=x&(((1<<30)-1)^((1<<bit)-1)); itv.pb({curl^curx,1}); itv.pb({(curl^curx)+(1<<bit)-1+1,-1}); //+1:闭区间 return; } int mid=(L+R)>>1; if(l<=mid) modify(L,mid,l,r,bit-1,x); if(r>mid) modify(mid+1,R,l,r,bit-1,x); } int main() { ios::sync_with_stdio(false);cin.tie(0);cout.tie(0); #ifdef lr001 freopen("/Users/somnus/Desktop/data/in.txt","r",stdin); #endif #ifdef lr002 freopen("/Users/somnus/Desktop/data/out.txt","w",stdout); #endif cin>>n; rep(i,1,n){ cin>>l[i]>>r[i]; } rep(i,1,n-1){ int u,v,val; cin>>u>>v>>val; edge[u].pb({v,val}); edge[v].pb({u,val}); } dfs(1,-1); rep(i,1,n) modify(0,(1<<30)-1,l[i],r[i],30,w[i]); sort(itv.begin(),itv.end()); int cnt=0; ll ans=0; rep(i,0,(int)itv.size()-2){ cnt+=itv[i].se; if(cnt==n){ ans+=itv[i+1].fi-itv[i].fi; } } cout<<ans<<'\n'; return 0; }