【UOJ#207】共价大爷游长沙

随机权值,LCT 维护子树信息

题目大意:

给定一棵树,和一个可重路径集合 $S$,要支持以下操作:

  1. 修改树上的一条边,保证操作合法且修改完仍然是一棵树。
  2. 将一条路径加入 $S$。
  3. 删除 $S$ 中的一条路径,保证原来存在。
  4. 给定一条边,询问是否 $S$ 中所有路径都经过这条边。

题解:

对于查询,我们如果把这条边断掉,那么树会分成两棵树。如果这条边必经,那么两棵树的任意一棵里,都包含 $S$ 中每条路径的恰好一个端点。

那么我们对每条路径都赋一个随机的权值 $v$,并对路径的两个端点都异或上 $v$。

然后用 LCT 实现动态加边删边,维护子树异或和。

查询的时候,如果断掉边后,一棵子树的异或和恰好等于当前 $S$ 中所有路径的异或和,则说明该边必经。否则就说明有一条路径两个端点都不在子树内或都在子树内(异或抵消),该边不必经。

出错的概率极低,可以忽略。

时间复杂度 $O((n+m)\log n)$。

Code:

#include<iostream>
#include<ctime>
#include<random>
using namespace std;
typedef unsigned long long LL;
const int N=3e5+8;
mt19937_64 mt(time(0));
int T,n,m;
int U[N],V[N],t0t;LL W[N];
namespace lct{
    int ch[N][2],fa[N],sz[N];
    LL val[N],v_[N],xv[N];
    bool tag[N];
    inline bool ckr(int x,int p=1){return ch[fa[x]][p]==x;}
    inline bool isroot(int x){return!(ckr(x)||ckr(x,0));}
    inline void flip(int x){swap(ch[x][0],ch[x][1]),tag[x]^=1;}
    inline void pushdown(int x){
        if(tag[x]){
            if(ch[x][0])flip(ch[x][0]);
            if(ch[x][1])flip(ch[x][1]);
            tag[x]=0;
        }
    }
    inline void update(int x){
        sz[x]=sz[ch[x][0]]+sz[ch[x][1]]+1;
        xv[x]=val[x]^v_[x]^xv[ch[x][0]]^xv[ch[x][1]];
    }
    inline void rotate(int x){
        int y=fa[x],z=fa[y],k=ckr(x);
        if(!isroot(y))ch[z][ckr(y)]=x;
        fa[x]=z,fa[y]=x,fa[ch[x][!k]]=y;
        ch[y][k]=ch[x][!k],ch[x][!k]=y,update(y);
    }
    inline void splay(int x){
        static int sta[N],top;sta[top=1]=x;
        for(int y=x;!isroot(y);sta[++top]=y=fa[y]);
        while(top)pushdown(sta[top--]);
        for(;!isroot(x);rotate(x))
            if(!isroot(fa[x]))rotate((ckr(x)^ckr(fa[x]))?x:fa[x]);
        update(x);
    }
    inline void access(int x){
        for(int t=0;x;ch[x][1]=t,t=x,x=fa[x])splay(x),v_[x]^=xv[t]^xv[ch[x][1]];
    }
    inline void makeroot(int x){access(x),splay(x),flip(x);}
    inline void split(int x,int y){makeroot(x),access(y),splay(y);}
    inline void link(int x,int y){makeroot(x),makeroot(y),fa[x]=y,v_[y]^=xv[x],update(y);}
    inline void cut(int x,int y){split(x,y),ch[y][0]=fa[x]=0,update(y);}
    inline void modify(int x,LL v){makeroot(x),val[x]^=v,update(x);}
    inline bool ask(int x,int y,LL X){
        cut(x,y);
        bool ans=xv[x]==X;
        link(x,y);
        return ans;
    }
}
int main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>T>>n>>m;
    for(int i=1;i<=n;++i)
        lct::sz[i]=1;
    for(int i=1;i<n;++i){
        int x,y;
        cin>>x>>y;
        lct::link(x,y);
    }
    LL X=0;
    while(m--){
        int op;
        cin>>op;
        switch(op){
            case 1:{
                       int x,y;
                       cin>>x>>y;
                       lct::cut(x,y);
                       cin>>x>>y;
                       lct::link(x,y);
                       break;
                   }
            case 2:{
                       int x,y;
                       cin>>x>>y;
                       U[++t0t]=x,V[t0t]=y,W[t0t]=mt();
                       lct::modify(x,W[t0t]);
                       lct::modify(y,W[t0t]);
                       X^=W[t0t];
                       break;
                   }
            case 3:{
                       int id;
                       cin>>id;
                       lct::modify(U[id],W[id]);
                       lct::modify(V[id],W[id]);
                       X^=W[id];
                       break;
                   }
            case 4:{
                       int x,y;
                       cin>>x>>y;
                       cout<<(lct::ask(x,y,X)?"YES":"NO")<<'\n';
                   }
        }
    }
    return 0;
}