返回顶部

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;
    }
    
    
posted @ 2021-07-29 17:23  Rayotaku  阅读(93)  评论(0编辑  收藏  举报