[BZOJ2959]长跑 LCT+双联通分量+并查集

首先在一个边双联通分量中所有点的贡献是都可以算入的。但是因为有加边操作,考虑用LCT来维护缩边双之后的树。
若一次连接的 (x,y) 在同一棵树上,就把 x y 的路径提出来,缩成一个点。这可能会导致其他某些点的父亲标号改变,所以调用父亲之前一定要用并查集更新一下。
然后因为只有加边,判断在不在一棵树上最好也用并查集,用找根操作常数略大。
代码:

#include
#include
#include
#define N 150010
using namespace std;
int n,m,fa[N],w[N],be[N];
int read()
{
    int x=0;char ch=getchar();
    for(;ch<'0'||ch>'9';ch=getchar());
    for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
    return x;
}
int getfa(int v)
{
    if(fa[v]==v) return v;
    return(fa[v]=getfa(fa[v]));
}
int getbe(int v)
{
    if(be[v]==v) return v;
    return (be[v]=getbe(be[v]));
}
struct tree;
tree *a[N];
struct tree
{
    int t,sum,id;
    tree *s[2],*f;
    bool rev;
    tree(){t=sum=0;rev=0;s[0]=s[1]=f=NULL;}
    inline bool nfa()
    {
        if(f) f=a[getfa(f->id)];
    }
    inline bool isroot()
    {
        nfa();
        return (!f)||(f->s[0]!=this&&f->s[1]!=this);
    }
    inline bool son()
    {
        return f->s[1]==this;
    }
    inline void update()
    {
        sum=t+(s[0]?s[0]->sum:0)+(s[1]?s[1]->sum:0);
    }
    inline void reverse()
    {
        rev^=1;swap(s[0],s[1]);
    }
    inline void pushdown()
    {
        if(rev)
        {
            if(s[0]) s[0]->reverse();
            if(s[1]) s[1]->reverse();
            rev=0;
        }
    }
    void rotate()
    {
        pushdown();
        bool b=son()^1;
        f->s[b^1]=s[b];
        if(s[b]) s[b]->f=f;
        s[b]=f;f=f->f;s[b]->f=this;
        if(f&&f->s[0]==s[b]) f->s[0]=this;
        else if(f&&f->s[1]==s[b]) f->s[1]=this;
        s[b]->update();
        update();
    }
    void splay()
    {
        while(!isroot())
            if(f->isroot())  f->pushdown(),rotate();
            else
            {
                f->f->nfa();
                f->f->pushdown();
                f->pushdown();
                if(son()^f->son()) f->rotate();
                else rotate();
                rotate();
            }
    }
    void access()
    {
        for(tree *x=this,*y=NULL;x;y=x,x->nfa(),x=x->f)
            x->splay(),x->pushdown(),x->s[1]=y,x->update();
    }
    void makeroot()
    {
        access();
        splay();
        reverse();
    }
    void link(tree *y)
    {
        makeroot();
        f=y;
    }
    void split(tree *y)
    {
        y->makeroot();
        access();
        splay();
    }
    void modify(int c)
    {
        splay();
        t=sum=c;
    }
    void merge(tree *x)
    {
        if(x!=this) x->t+=t;
        if(getfa(x->id)!=getfa(id)) fa[fa[id]]=fa[x->id];
        if(s[0]) s[0]->merge(x);
        if(s[1]) s[1]->merge(x);
        s[0]=s[1]=f=NULL;
        if(x!=this)t=sum=0;
        rev=0;  
    }
};
int main()
{
    n=read();m=read();
    for(int i=1;i<=n;i++)
    {
        w[i]=read();
        (a[i]=new tree)->modify(w[i]);
        a[i]->id=fa[i]=be[i]=i;
    }
    while(m--)
    {
        int opt=read(),x=read(),y=read();       
        if(opt!=2) x=getfa(x),y=getfa(y);
        if(opt==1)
        {
            if(getbe(x)!=getbe(y)) be[be[y]]=be[x],a[x]->link(a[y]);
            else if(x!=y)
                {
                    a[x]->split(a[y]);
                    a[x]->merge(a[x]);   
                }
        }
        if(opt==2)
        {
            getfa(x);
            a[fa[x]]->modify(a[fa[x]]->t-w[x]+y);
            w[x]=y;
        }
        if(opt==3)
        {
            if(getbe(x)!=getbe(y)) {puts("-1");continue;}
            a[x]->split(a[y]);
            printf("%d\n",a[x]->sum);
        }
    }   
    return 0;
}

你可能感兴趣的:(并查集,动态树,双连通分量)