HDU 6200 mustedge ACM/ICPC 2017 Shenyang Online(LCT动态缩点)

mustedge mustedge mustedge

Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 474    Accepted Submission(s): 93

Problem Description

Give an connected undirected graph with n nodes and m edges, ( n,m105 ) which has no selfloops or multiple edges initially .
Now we have q operations ( q105 ): 
1 u v : add an undirected edge from u to v ; (uv&&1u,vn)
2 u v : count the number of mustedges from u to v ; (1u,vn) .
mustedge : we define set Ei as a path from u to v which contain edges in this path, and |k1Ei| is the number of mustedges . |x| means size of set x , and E1,E2Ek means all the paths.
It's guaranteed that n,m,q106
Please note that maybe there are more than one edges between two nodes after we add edges. They are not the same, which means they can be in a set at the same time. Read the sample data for more information.

Input

Input starts with an integer T , denoting the number of test cases.
For each case:
First line are two number n and m ;
Then next m lines, each contains two integers u and v , which indicates an undirected edge from u to v ;
Next line contains a number q , the number of operations;
Then next q lines, contains three integers x , u and v where x is the operation type, which describes an operation

Output

For each test case, output "Case #x:" where x is the test case number starting from 1.
In each test case, print a single number one line when query the number of mustedges .

Sample Input

 
 
2 4 3 1 2 2 3 3 4 5 2 1 4 1 2 3 2 1 4 2 2 3 2 2 4 8 9 1 2 2 3 1 3 3 4 4 5 4 6 5 7 5 8 7 8 5 2 7 8 2 1 6 2 4 7 1 6 8 2 5 6

Sample Output

 
 
Case #1: 3 2 0 1 Case #2: 0 2 1 0

Source

2017 ACM/ICPC Asia Regional Shenyang Online



        说实话,之前一直觉得LCT的用法大致都知道了,比完赛才发现LCT还有这种操作……

        大致题意:给你一个无向图,初始时没有重边和自环,然后可以支持不断的加边和询问操作。每次询问时,输出u到v的所有路径边集的交集大小。

        所谓边集交集大小,即如果有u、v有两条完全不同的路径可以到达,那么这个交集大小就是0。如果有重合的部分,那么就是重合的边的数量。其实,这个交集如果没有考虑好确实也不知道该怎么处理。

        这里我们先考虑两点之间若只有一条路径的情况,此时结果就是点的数量减去一。然后我们再考虑,如果有多条边那是什么情况?肯定是中间出现了环。如果u、v本身就在环上,那么结果就是0;如果不在,即路径有部分在环上,不同的路径走了环的不同侧,那么显然,所有环上的点都不能计入结果。举个例子:有边u->环->v,那么结果就是2=3-1,即只计算从u到环和从环到v的边。机智的读者或许已经发现,对于这个环,内部的所有东西都是没有用的,我们要做的只是缩点即可。把所有的环缩成一个新的点,然后每次只需要统计u到v的路径上有多少个点,最后点的数量减去1就是答案。

        但是,这里由于是动态的加边,所以正常的tarjan缩点并不能派上用场。于是,才知道了可以用LCT动态缩点。那么,如何操作呢?其实非常简单,对于每一个点,设置一个f数组,f[i]表示i点经过缩点之后的点的标号。然后每次加边,如果两个端点已经在LCT中,那么就把两点之间所有的点的f编号暴力修改为新的一个点,这样就完成了缩点操作。值得注意的是,对于f数组我们要使用并查集维护,因为缩点之后的新的点有可能再次被缩点,所以用上并查集。相应的,我们的树的关系fa在使用的时候时刻要用上find,把对应点的缩点之后的真实标号作为fa。具体见代码:

#include<bits/stdc++.h>
#define N 200010
using namespace std;

stack<int> sta;
int n,m,f[N];

int find(int x){return f[x]==x?x:(f[x]=find(f[x]));}

struct Link_Cut_Tree
{
    int son[N][2],fa[N],num[N],sum[N];
    bool rev[N];

    inline bool which(int x){return son[find(fa[x])][1]==x;}
    bool isroot(int x){return !find(fa[x])||son[find(fa[x])][which(x)]!=x;}

    void init()
    {
        memset(fa,0,sizeof(fa));
        memset(rev,0,sizeof(rev));
        memset(son,0,sizeof(son));
    }

    inline void push_up(int x)
    {
        if (!x) return; sum[x]=num[x];
        if (son[x][0]) sum[x]+=sum[son[x][0]];
        if (son[x][1]) sum[x]+=sum[son[x][1]];
    }

    inline void Reverse(int x)
    {
        if (!x) return;
        swap(son[x][0],son[x][1]);
        rev[x]^=1;
    }

    inline void push_down(int x)
    {
        if (!x||!rev[x]) return;
        Reverse(son[x][0]);
        Reverse(son[x][1]);
        rev[x]=0;
    }

    inline void Rotate(int x)
    {
        int y=find(fa[x]),z=find(fa[y]); bool ch=which(x);
        son[y][ch]=son[x][ch^1]; son[x][ch^1]=y;
        if (!isroot(y)) son[z][which(y)]=x;
        fa[x]=z; fa[y]=x; fa[son[y][ch]]=y;
        push_up(y); push_up(x);
    }

    inline void splay(int x)
    {
    	int i=x;
        for(;!isroot(i);i=find(fa[i]))
            sta.push(i); sta.push(i);
        while (!sta.empty())
        {
            push_down(sta.top());
            sta.pop();
        }
        while (!isroot(x))
        {
            int y=find(fa[x]);
            if (!isroot(y))
            {
                if (which(x)^which(y)) Rotate(x);
                                  else Rotate(y);
            }
            Rotate(x);
        }
    }

    inline void access(int x)
    {
        int y=0;
        while (x)
        {
            splay(x); son[x][1]=y;
            push_up(x); y=x; x=find(fa[x]);
        }
    }

    void beroot(int x){access(x);splay(x);Reverse(x);}

    inline int getroot(int x)
    {
        access(x); splay(x);
        while (son[x][0]) x=son[x][0];
        return x;
    }

    inline void rebuild(int x,int y)
    {
        if(!x)return; f[f[x]]=y;
        rebuild(son[x][0],y);
        rebuild(son[x][1],y);
    }

    inline void link(int x,int y)
    {
        beroot(x); fa[x]=y;
    }

} LCT;

int main()
{
    int T_T,T;
    cin>>T_T;T=T_T;
    while(T_T--)
    {
        LCT.init();
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
            LCT.num[i]=LCT.sum[i]=1,f[i]=i;
        for(int i=1;i<=m;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            u=find(u); v=find(v);
            if (LCT.getroot(u)!=LCT.getroot(v)) LCT.link(u,v);			//如果没连上,则直接连
            else
            {
                if (u==v) continue;						//如果对应的缩点后真是标号相同,那么不用再缩点
                LCT.beroot(u);
                LCT.access(v);
                LCT.splay(v);
                f[++n]=n;LCT.rebuild(v,n);					//rebuild暴力修改路径上所有点的缩点后标号
                LCT.num[n]=LCT.sum[n]=1;

            }
        }
        int q; scanf("%d",&q);
        printf("Case #%d:\n",T-T_T);
        while(q--)
        {
            int op,x,y;
            scanf("%d%d%d",&op,&x,&y);
            x=find(x); y=find(y);
            if (op==2)
            {
                if (x==y) {puts("0"); continue;}
                LCT.beroot(x);
                LCT.access(y);
                LCT.splay(y);
                printf("%d\n",LCT.sum[y]-1);
            } else
            {
                if (LCT.getroot(x)!=LCT.getroot(y)) LCT.link(x,y);
                else
                {
                    if (x==y) continue;
                    LCT.beroot(x);
                    LCT.access(y);
                    LCT.splay(y);
                    f[++n]=n;LCT.rebuild(y,n);
                    LCT.num[n]=LCT.sum[n]=1;
                }
            }
        }
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值