您的位置:首页 > 其它

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

2017-09-12 19:21 423 查看

mustedge mustedge mustedge

[b]Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)

Total Submission(s): 474    Accepted Submission(s): 93
[/b]

Problem Description
Give an connected undirected graph with
n
nodes and m
edges, (n,m≤105)
which has no selfloops or multiple edges initially.

Now we have q
operations (q≤105): 
⋅1 u v:
add an undirected edge from u
to v;
(u≠v&&1≤u,v≤n)
⋅2 u v:
count the number of mustedges
from u
to v;
(1≤u,v≤n).
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,E2…Ek
means all the paths.

It's guaranteed that ∑n,∑m,∑q≤106

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
;

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

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

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
=LCT.sum
=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
=LCT.sum
=1;
}
}
}
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐