求给定权重范围,求树的可能存在种数的问题

原题链接
给出n个点的一棵树,每个点的权值范围,以及n-1条边和这条边的两个节点的权值异或值。求这棵树的可能存在种数。
分析:
由异或性质:A ^ B=C,则A ^ B ^ B=A=C ^ B。
以1为根节点,若val[1]=0,则可以直接dfs求出其他节点的值val[i]。若val[1]=a,易知val’[i]=val[i]^a。
因此,求这棵树的可能存在种数,即是求a的可能种数。
要使l[i]<=val’[i]<=r[i],即l[i]<=val[i]^a<=r[i],故a的种数即是满足 l[i]<=val[i]^a<=r[i]的a的所有种数。
要求l[i]<=val[i]^a<=r[i],不如先求 1<=val[i]^a<=l[i]-1, 并将此时a(初始范围为1至正无穷)的可能存在范围标记-1,1<=val[i]^a<=r[i],并将此时a的可能存在范围标记1。这样,范围里值为1的就是符合此val[i]对应的a的范围。最终范围里值为n的就是a的总范围。
枚举x=val[i]^a的值,可以用dfs加二进制法,例如对于 1<=val[i]^a<=r,此时遍历到j位,则r和val[i]的j位分别可能是11、10、01、00。记录此时a的值已经是nowA。
11:此时若a的j位为1,则此时x的j位为1^1=0,故此后x必定<=r,则a的范围区间[nowA+(1<<j),nowA+(1<<(1+j))-1]必定符合,然后再考虑a的j位为0的情况,dfs下去。
其他情况同理。
标记范围可以用类似扫描线的方法,结合线段差分的思想。

int l[maxv],r[maxv],val[maxv];
bool vis[maxv];
void dfs(int p){
    vis[p]=1;
    for(int i=0;i<g[p].size();i++){
        int ver=g[p][i].first,edg=g[p][i].second;
        if(vis[ver]) continue;
        val[ver]=val[p]^edg;
        dfs(ver);
    }
}
vector<pair<int,int> > all[2],res;
void dfs1(int id,int now,int val,int nowR,int nowA){
    if(now==-1){
        all[id].push_back({nowA,nowA});
        return;
    }
    int x=((val>>now)&1),y=((nowR>>now)&1);
    if(y){
        if(x) all[id].push_back({nowA+(1<<now),nowA+(1<<(1+now))-1}),dfs1(id,now-1,val,nowR,nowA);
        else all[id].push_back({nowA,nowA+(1<<now)-1}),dfs1(id,now-1,val,nowR,nowA+(1<<now));
    }else{
        if(x) dfs1(id,now-1,val,nowR,nowA+(1<<now));
        else dfs1(id,now-1,val,nowR,nowA);
    }
}
int main(){
    int n,u,v,w,ans=0,sum=0;
    n=(int)read();
    for(int i=1;i<=n;i++) l[i]=(int)read(),r[i]=(int)read();
    for(int i=1;i<=n-1;i++) u=(int)read(),v=(int)read(),w=(int)read(),g[u].push_back(make_pair(v,w)),g[v].push_back(make_pair(u,w));
    dfs(1);  //求val[i]
    for(int i=1;i<=n;i++){  //求1<=val[i]^a<=l[i]-1与1<=val[i]^a<=r[i]
        if(l[i]>=1) dfs1(0,29,val[i],l[i]-1,0);
        dfs1(1,29,val[i],r[i],0);
    }
    for(int i=0;i<all[0].size();i++){
        int l=all[0][i].first,r=all[0][i].second;
        res.push_back({l,-1}),res.push_back({r+1,1});
    }
    for(int i=0;i<all[1].size();i++){
        int l=all[1][i].first,r=all[1][i].second;
        res.push_back({l,1}),res.push_back({r+1,-1});
    }
    sort(res.begin(),res.end());  //扫描线差分法求值为n的区间。
    for(int i=0;i<res.size()-1;i++){
        sum+=res[i].second;
        if(sum==n) ans+=res[i+1].first-res[i].first;
    }
    printf("%d",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值