2021牛客暑期多校训练营4 E-Tree Xor(异或+思维+区间交 or Trie树)

这篇博客介绍了E-TreeXor问题,即如何处理带有区间限制的异或操作。文章指出,当给定一个点的权值后,其他点的权值可以确定,并且在特定条件下,可以将区间限制转化为不连续的子区间。作者通过线段树和位运算,将问题转换为寻找满足特定异或范围的不连续区间,并提供了两种解决方案:一是通过线段树处理不合法区间;二是利用字典树(Trie树)求解。文章最后提供了两种方法的C++实现代码。
摘要由CSDN通过智能技术生成

E-Tree Xor

首先不考虑区间限制条件,我们给定其中一个点的权值后,那么其他点的权值也就确定。比如 val 1 = 0 \text{val}_1=0 val1=0,即可通过变得限制求出其他点 val u \text{val}_u valu,而且不难发现如果 val 1 = 0 ⊕ a \text{val}_1=0\oplus a val1=0a那么其余点的权值 val u = 0 ⊕ a \text{val}_u=0\oplus a valu=0a

下面记 val u \text{val}_u valu为当 val 1 = 0 \text{val}_1=0 val1=0时,u点的权值。

于是问题转化为求出满足下面区间限制 a a a的个数。
L 1 ≤ val 1 ⊕ a ≤ R 1 L 2 ≤ val 2 ⊕ a ≤ R 2 … L n ≤ val n ⊕ a ≤ R n \text L_1\leq \text{val}_1\oplus a\leq \text R_1\\\text L_2\leq \text{val}_2\oplus a\leq \text R_2\\ \dots\\ \text L_n\leq \text{val}_n\oplus a\leq \text R_n L1val1aR1L2val2aR2LnvalnaRn

考虑其中一种限制直观的想法是 L u ≤ val u ⊕ a ≤ R u ⇒ L 1 ⊕ val 1   ≤ a ≤ R 1 ⊕ val 1 \text L_u\leq \text{val}_u\oplus a\leq \text R_u\Rightarrow \text L_1\oplus \text{val}_1\ \leq a\leq \text R_1\oplus \text{val}_1 LuvaluaRuL1val1 aR1val1不难发现上面转化是错误的,而且可以发现 a a a的区间是不连续的!!!需要找出这些不连续的区间!

于是就很难处理,后面就是讲题人的做法(真滴强)


我们可以利用 [ 0 , 2 30 − 1 ] [0,2^{30}-1] [0,2301] 的线段树, 把 [ L i , R i ] [L_i , R_i] [Li,Ri] 分成 log ⁡ W \log W logW个连续的区间, 且每个区间的形式是 : [ ∗ ∗ ∗ 00 … 00 , ∗ ∗ ∗ 11 … 11 ] [***00\dots00,***11\dots 11] [0000,1111], 这样的区间异或上 val i \text{val}_i vali仍然还是一个区间

不妨设 ∗ ∗ ∗ *** 的数量是k

不难发现区间 [ ∗ ∗ ∗ 00 … 00 , ∗ ∗ ∗ 11 … 11 ] [***00\dots00,***11\dots 11] [0000,1111]抑或上 val i \text{val}_i vali的区间是 [ − − − 00 … 00 , − − − , 11 … 11 ] [- - -00\dots00,---,11\dots11] [0000,,1111]
其中 − − − --- ( ∗ ∗ ∗ 00 … 00 ) ⊕ val i (***00\dots00)\oplus \text{val}_i (0000)vali的前 k k k位的值
比如: [ 1010 0000 , 1010 1111 ] [\color{Blue}{1010} \color{Red}{0000},\color{Blue}{1010} \color{Red}{1111}] [10100000,10101111] ⊕ 1001 1010 ⇒ \oplus\color{Blue}{1001} \color{Red}{1010}\Rightarrow 10011010 [ 0011 0000 , 0011 1111 ] [\color{Blue}{0011} \color{Red}{0000},\color{Blue}{0011} \color{Red}{1111}] [00110000,00111111]

蓝色部分异或 0011 = 1010 ⊕ 0011 \color{Blue}0011=1010\oplus0011 0011=10100011原红色部分不变,稍微思考一下就知道为什么了。

通过上面操作成功找出这些不连续的区间!!!


然后就sort区间差分乱搞就行。时间复杂度2log,可以用线段树维护所有不合法区间的并集,把不合法的区间标记为1,把时间复杂度降为1log

Code1
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
template <class T=int> T rd()
{
    T res=0;T fg=1;
    char ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') fg=-1;ch=getchar();}
    while( isdigit(ch)) res=(res<<1)+(res<<3)+(ch^48),ch=getchar();
    return res*fg;
}
const int N=100010;
int h[N],e[2*N],ne[2*N],w[2*N],idx;
void add(int a,int b,int c){e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;}
int L[N],R[N];
int val[N];
struct node
{
    int l,r;
}tree[N*40];
int rt,cnt,n;
vector<pair<int,int>> vec;
void insert(int &u,int l,int r,int L,int R,int val)
{
    if(!u) u=++cnt;
    if(L<=l&&r<=R)
    {
        int ql=l^(val&(~(r-l)));//异或之后的区间[ql,qr]
        int qr=ql+r-l;
        vec.push_back({ql,1});
        vec.push_back({qr+1,-1});
        return;
    }
    int mid=l+r>>1;
    if(L<=mid) 
        insert(tree[u].l,l,mid,L,R,val);
    if(R>mid)
        insert(tree[u].r,mid+1,r,L,R,val);
}

void dfs(int u,int fa)
{
    insert(rt,0,(1<<30)-1,L[u],R[u],val[u]);
    for(int i=h[u];i!=-1;i=ne[i])
    {
        int v=e[i];
        if(v==fa) continue;
        val[v]=val[u]^w[i];
        dfs(v,u);
    }
}
int solve()
{
    sort(vec.begin(),vec.end());
    vec.push_back({1<<30,0});
    int ans=0;
    int cur=0;
    for(int i=0;i<vec.size()-1;i++)
    {
        cur+=vec[i].second;
        if(cur==n) ans+=vec[i+1].first-vec[i].first;
    }
    return ans;
}
int main()
{
    n=rd();
    for(int i=1;i<=n;i++) L[i]=rd(),R[i]=rd();
    memset(h,-1,sizeof h);
    for(int i=1;i<n;i++)
    {
        int u=rd(),v=rd(),w=rd();
        add(u,v,w),add(v,u,w);
    }
    
    dfs(1,0);
    printf("%d\n",solve());
}

比较容易想到的做法就是我们需要求
L 1 ≤ val 1 ⊕ a ≤ R 1 L 2 ≤ val 2 ⊕ a ≤ R 2 … L n ≤ val n ⊕ a ≤ R n \text L_1\leq \text{val}_1\oplus a\leq \text R_1\\\text L_2\leq \text{val}_2\oplus a\leq \text R_2\\ \dots\\ \text L_n\leq \text{val}_n\oplus a\leq \text R_n L1val1aR1L2val2aR2LnvalnaRn
转化成
[ val 1 ⊕ a ≤ R 1 ] ➖ [ val 1 ⊕ a < L 1 ] [ val 2 ⊕ a ≤ R 2 ] ➖ [ val 2 ⊕ a < L 2 ] … [ val n ⊕ a ≤ R n ] ➖ [ val n ⊕ a < L n ] [\text{val}_1\oplus a\leq \text R_1]➖[\text{val}_1\oplus a< \text L_1]\\ [\text{val}_2\oplus a\leq \text R_2]➖[\text{val}_2\oplus a< \text L_2]\\ \dots\\ [\text{val}_n\oplus a\leq \text R_n]➖[\text{val}_n\oplus a< \text L_n] [val1aR1][val1a<L1][val2aR2][val2a<L2][valnaRn][valna<Ln]
上面的➖理解为两个集合相减。

实际上我需要求的就是 ⊕ a ≤ b \oplus a\leq b ab的区间,显然字典树可以做,相当于求 ⊕ a ≤ b \oplus a\leq b ab的数有哪些,和第二次杭电I love counting求的步骤一样,在Trie树上讨论一下就行。

Code2
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
template <class T=int> T rd()
{
    T res=0;T fg=1;
    char ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') fg=-1;ch=getchar();}
    while( isdigit(ch)) res=(res<<1)+(res<<3)+(ch^48),ch=getchar();
    return res*fg;
}
const int N=100010;
int h[N],e[2*N],ne[2*N],w[2*N],idx;
void add(int a,int b,int c){e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;}
int L[N],R[N];
int val[N];
struct node
{
    int l,r;
}tree[N*40];
int rt,cnt,n;
vector<pair<int,int>> seg[2];
void query(int k,int a,int b)// x^a<=b
{
    int u=rt;
    int cur=0;
    for(int i=29;i>=0;i--)//30位
    {
        int ai=a>>i&1;
        int bi=b>>i&1;
        if(bi==1) //b=1
        {
            if(ai==0) 
            {
                seg[k].push_back({cur,cur+(1<<i)-1});
                cur+=1<<i;
                if(!tree[u].r) tree[u].r=++cnt;
                u=tree[u].r;
            }
            else
            {
                seg[k].push_back({cur+(1<<i),cur+(1<<i)+(1<<i)-1});
                
                if(!tree[u].l) tree[u].l=++cnt;
                u=tree[u].l;
            }
        }
        else
        {
            if(ai==0)
            {
                if(!tree[u].l) tree[u].l=++cnt;
                u=tree[u].l;
            }
            else 
            {
                cur+=1<<i;
                if(!tree[u].r) tree[u].r=++cnt;
                u=tree[u].r;
            }
        }
    }
    seg[k].push_back({cur,cur});
}
void dfs(int u,int fa)
{
    for(int i=h[u];i!=-1;i=ne[i])
    {
        int v=e[i];
        if(v==fa) continue;
        val[v]=val[u]^w[i];
        dfs(v,u);
    }
}
int solve()
{
    vector<pair<int,int>>vec;
    for(auto t:seg[0]) 
    {
        vec.push_back({t.first,-1});
        vec.push_back({t.second+1,+1});
    }
    for(auto t:seg[1]) 
    {
        vec.push_back({t.first,+1});
        vec.push_back({t.second+1,-1});
    }
    sort(vec.begin(),vec.end());
    
    vec.push_back({1<<30,0});
    int ans=0;
    int cur=0;
    for(int i=0;i<vec.size()-1;i++)
    {
        cur+=vec[i].second;
        if(cur==n) ans+=vec[i+1].first-vec[i].first;
    }
    return ans;
}
int main()
{
    n=rd();
    for(int i=1;i<=n;i++) L[i]=rd(),R[i]=rd();
    memset(h,-1,sizeof h);
    for(int i=1;i<n;i++)
    {
        int u=rd(),v=rd(),w=rd();
        add(u,v,w),add(v,u,w);
    }
    
    dfs(1,0);
    for(int i=1;i<=n;i++) 
    {
        if(L[i]>0) query(0,val[i],L[i]-1);
        query(1,val[i],R[i]);
    }
    printf("%d\n",solve());
}

其实仔细分析应该能写出Trie树的做法,不过当时没有分析出来啊www

要加油哦~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值