(Luguo) P3401 洛谷树 (树链剖分+线段树)

本文介绍了一种解决特定类型图论问题的方法,即计算图中两点间所有子路径的异或和。通过预处理每个节点到根节点的异或值,结合数链剖分技术,有效地解决了大规模数据集上的查询和更新操作。文章详细阐述了使用线段树维护每一位状态的技巧,并提供了完整的C++实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

传送门

:首先异或也是具有前缀性质的,b[i]代表从i点异或到根节点的值,那么i到j的异或和就是b[i]^b[j],但是题目求得是i~j路径上所以子路径的异或和,显然是不能一个一个的求,思路也很巧妙,我们将这条路径上所有的b[]拿出来,对每一位进行维护,看看这些数在这一位上有多少1,和0,那么这一位对结果的贡献不就是(1<<i)*num1*num0吗;还有修改,首先修改影响的肯定是该节点下面子树所有的节点,修改还是每一位每一位的看,如果有一位的1变成了0,那说明0的个数和1的个数互换,rev一下就可以。这里用线段树去维护每一位就可以了。剩下的部分就是数链剖分的基本操作了。

#include<bits/stdc++.h>
#define il inline
#define pb push_back
#define ms(_data,v) memset(_data,v,sizeof(_data))
#define SZ(a) int((a).size())
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const int N=5e4+5;
//il int Add(ll &x,ll y) {return x=x+y>=mod?x+y-mod:x+y;}
//il int Mul(ll &x,ll y) {return x=x*y>=mod?x*y%mod:x*y;}
int n,q,ed[N];
struct node{int to,w;};
vector<node> eg[N];
int dep[N],fa[N],sz[N],son[N],id[N],bel[N],cnt=0;
int b[N],nb[N]; //b[i]:i到根异或的结果
bool vis[N];
void dfs1(int x,int ff){
	vis[x]=true;
    dep[x]=dep[ff]+1;
    fa[x]=ff,sz[x]=1;
    int mx=-1,to;
    for(int i=0;i<SZ(eg[x]);++i){
		to=eg[x][i].to;
		if(to==ff || vis[to]) continue;
		b[to]=b[x]^eg[x][i].w;
		ed[to]=eg[x][i].w;
		dfs1(to,x);
		sz[x]+=sz[to];
		if(sz[to]>mx)	son[x]=to,mx=sz[to];
	}
}
void dfs2(int x,int topx){
    id[x]=++cnt;
    bel[x]=topx,nb[cnt]=b[x];
    if(!son[x]) return ;
    dfs2(son[x],topx);
    for(int i=0,to;i<SZ(eg[x]);++i){
        to=eg[x][i].to;
        if(to==fa[x] || to==son[x]) continue;
        dfs2(to,to);
    }
}
struct T{
    int n1,n0;
    bool rev;
    T(){n1=0,n0=0,rev=0;}
}t[15][N<<3];
il T add(T a,T b){
    T res;
    res.n1=a.n1+b.n1;
    res.n0=a.n0+b.n0;
    return res;
}
void pushdown(int k,int rt){
    if(t[k][rt].rev){
        swap(t[k][rt<<1].n1,t[k][rt<<1].n0);
        swap(t[k][rt<<1|1].n1,t[k][rt<<1|1].n0);
        t[k][rt<<1].rev^=1;
        t[k][rt<<1|1].rev^=1;
        t[k][rt].rev=0;
    }
}
void build(int l,int r,int rt,int k){
//	cout<<"build "<<l<<" "<<r<<" "<<rt<<" "<<k<<endl;
    if(l==r){
        if((nb[l]>>k)&1) t[k][rt].n1=1,t[k][rt].n0=0;
        else t[k][rt].n1=0,t[k][rt].n0=1;
        return ;
    }
    int mid=(l+r)>>1;
    build(l,mid,rt<<1,k),build(mid+1,r,rt<<1|1,k);
    t[k][rt]=add(t[k][rt<<1],t[k][rt<<1|1]);
}
void rev(int l,int r,int L,int R,int rt,int k){
    if(L<=l && R>=r){
        swap(t[k][rt].n1,t[k][rt].n0);
        t[k][rt].rev^=1;
        return ;
    }
    pushdown(k,rt);
    int mid=(l+r)>>1;
    if(L<=mid) rev(l,mid,L,R,rt<<1,k);
    if(R>mid) rev(mid+1,r,L,R,rt<<1|1,k);
    t[k][rt]=add(t[k][rt<<1],t[k][rt<<1|1]);
}
void update(int u,int w){
    for(int i=0;i<=10;++i){
        if(((w^ed[u])>>i)&1) rev(1,n,id[u],id[u]+sz[u]-1,1,i);
    }
    ed[u]=w;
}
T query(int l,int r,int L,int R,int rt,int k){
    if(L<=l && R>=r){
        return t[k][rt];
    }
    int mid=(l+r)>>1;
    pushdown(k,rt);
    T res,ls,rs;
    if(L<=mid){
        ls=query(l,mid,L,R,rt<<1,k);
        res.n0+=ls.n0,res.n1+=ls.n1;
    }
    if(R>mid){
        rs=query(mid+1,r,L,R,rt<<1|1,k);
        res.n0+=rs.n0,res.n1+=rs.n1;
    }
    return res;
}
ll query(int u,int v){
    ll res=0;
    int pu=u,pv=v;
    for(int i=0;i<=10;++i){
        u=pu,v=pv;
        T rs,tp;
        while(bel[u]!=bel[v]){
            if(dep[bel[u]]<dep[bel[v]]) swap(u,v);
            tp=query(1,n,id[bel[u]],id[u],1,i);
            rs=add(rs,tp);
            u=fa[bel[u]];
        }
        if(dep[u]>dep[v]) swap(u,v);
        rs=add(rs,query(1,n,id[u],id[v],1,i));
        res+=(1LL<<i)*rs.n0*rs.n1;
    }
    return res;
}
int main(){
    std::ios::sync_with_stdio(0);cin.tie(0);
    cin>>n>>q;
    int u,v,w,op;
    for(int i=1;i<=n-1;++i){
        cin>>u>>v>>w;
        eg[u].pb(node{v,w});
        eg[v].pb(node{u,w});
    }
    dfs1(1,0);
	dfs2(1,1);
    for(int i=0;i<=10;++i) build(1,n,1,i);
    while(q--){
        cin>>op>>u>>v;
        if(op==1) cout<<query(u,v)<<endl;
        else{
            cin>>w;
            if(u!=fa[v]) swap(u,v);
            update(v,w);
        }
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值