2018TYUT暑期ACM模拟赛(3)
​​​mustedge mustedge mustedge HDU - 6200​​​
这道题是完全看题解写的然后权当学习新算法和思想。
题意:有n组测试数据,n个点m条边。输入u,v表示u到v有变。接下来q个操作:
1 :u,v表示加上u,v的边
2:u,v查询u到v的”mustedge“有多少条。
思路:
1、Tarjan算法无向图缩点
2、用dfs表示成一棵树存父节点和深度
3、LCA寻找公共祖先,记录寻找过程中经历过的路径,即就是求的2要求的答案
4、加边使得两点及其寻找公共祖先路径上的点,再压缩成一个点并且修改夫父节点
5、中间找父节点什么类似用了并查集
代码里面有详细的注释
​​​这是我参考的题解 * -_- *​​​
​​​这是我学习Tarjan的网址​

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
//Tarjan
const int MAXN=200100;
const int MAXM=500100;
struct Edge{
int to,next;
int flag,from;
}edge[MAXM];
int head[MAXN],tot;
int Low[MAXN],DFN[MAXN],Stack[MAXN],Belong[MAXN];//belong数组的值是1-scc
//DFN[]作为这个点搜索的次序编号(时间戳),简单来说就是 第几个被搜索到的。%每个点的时间戳都不一样%。
//作为每个点在这颗树中的,最小的子树的根,每次保证最小,like它的父亲结点的时间戳这种感觉。如果它自己的LOW[]最小,那这个点就应该从新分配,变成这个强连通分量子树的根节点
int Index,top;
int scc;//强连通分量的个数
bool Instack[MAXN];
int num[MAXN];//各个强连通分量包含的点个数,数组编号1-scc
int vis[MAXN];
int deep[MAXN],f[MAXN],uf[MAXN];
void addegde(int u,int v)
{
edge[tot].from=u;edge[tot].flag=false;//修改
edge[tot].to=v;edge[tot].next=head[u];head[u]=tot++;
}
void Tarjan(int u,int pre)//本题是无向图,所以构造强连通分量为一个点的时候,不能又回到自身
{
int v;
Low[u]=DFN[u]=++Index;
Stack[top++]=u;
Instack[u]=true;
for(int i=head[u];~i;i=edge[i].next)
{
v=edge[i].to;
if(v==pre) continue;
if(!DFN[v])
{
Tarjan(v,u);//
if(Low[u]>Low[v]) Low[u]=Low[v];

if(Low[v]>DFN[u]) edge[i].flag=true;//下面有点有边

}
else if(Instack[v]&&Low[u]>DFN[v])
Low[u]=DFN[v];
}
if(Low[u]==DFN[u])//每次找到一个新点,这个点LOW[]=DFN[]。
{
scc++;
do
{
v=Stack[--top];
Instack[v]=false;
Belong[v]=scc;//v属于找到的第scc个的强连通里面的点
num[scc]++;//第scc强连通里面有几个点
}while(v!=u);
}
}
void dfs(int u,int d)
{
deep[u]=d;
vis[u]=true;
for(int i=head[u];~i;i=edge[i].next)
{
int v=edge[i].to;
if(vis[v]) continue;
f[v]=u;//记录父节点
dfs(v,d+1);//深度加一
}
}
void solve(int N)
{
memset(DFN,0,sizeof(DFN));
memset(Instack,false,sizeof(Instack));
memset(num,0,sizeof(num));
Index=scc=top=0;
for(int i=1;i<=N;i++)
if(!DFN[i])
Tarjan(i,i);
//重建链表,将强连通分量看作一个点,连成一条树 Tarjan 已经保证这是一个没有环的树
int t=tot;tot=0;
memset(head,-1,sizeof(head));
for(int i=0;i<t;i++)
if(edge[i].flag)//
{
int u=Belong[edge[i].from],v=Belong[edge[i].to];//
addegde(u,v);//覆盖了
addegde(v,u);
}
memset(vis,false,sizeof(vis));

dfs(1,0);f[1]=1;//dfs重新建图保存每个点的父节点,以及树的深度
for(int i=1;i<=scc;i++)
uf[i]=i;

}
void init()
{
tot=0;
memset(head,-1,sizeof(head));
}
int find(int u)//查找父节点
{
return uf[u]==u?u:uf[u]=find(uf[u]);//c
}
//对于操作2,进行搜索已经建立好的树
int LCA(int u,int v)//公共祖先最近公共祖先
{
u=find(u);//找u的父节点
v=find(v);
int ans=0;
while(u!=v)//两者最近的父节点不同
{
while(deep[u]>deep[v])
{
u=f[u];
u=find(u);
ans++;//表表示u到达v的深度要走几条边
}
while(deep[u]<deep[v])
{
v=f[v];
v=find(v);
ans++;
}
if(u==v) break;//说明u,v某一个为另一个的公共父节点
if(deep[u]==deep[v])
{
u=f[u]; u=find(u);
ans++;
v=f[v];v=find(v);
ans++;
}
}
return ans;
}
//对于操作1添加边
void ufch(int u,int v)
{
u=find(u);//找到这两个点的父节点
v=find(v);
int nu=u,nv=v;
while(u!=v)
{
while(deep[u]>deep[v])
u=f[u],u=find(u);
while(deep[u]<deep[v])
v=f[v],v=find(v);
if(u==v) break;
if(deep[u]==deep[v])
{
u=f[u];u=find(u);
v=f[v];v=find(v);
}

}//找到公共的
int gt=u;
u=nu,v=nv;
while(deep[u]>deep[gt])
{
int t=f[u];
uf[u]=gt;//把他们的父节点都变成公共的
u=t;u=find(u);
}
while(deep[v]>deep[gt])
{
int t=f[v];
uf[v]=gt;
v=t;v=find(v);
}//讲uv以及他们之间的路径连成一个点
}
int main()
{
int T;
int n,m;
int a,b,c,q;
scanf("%d",&T);
for(int t=1;t<=T;t++)
{
init();
scanf("%d%d",&n,&m);
for(int i=0;i<m;i++)
{
scanf("%d%d",&a,&b);
addegde(a,b);
addegde(b,a);//无向图
}
solve(n);
scanf("%d",&q);
printf("Case #%d:\n",t);
while(q--)
{
scanf("%d%d%d",&a,&b,&c);
b=Belong[b];//属于哪一个强连通
c=Belong[c];
if(a==1)
{
ufch(b,c);
}
else if(a==2)
printf("%d\n",LCA(b,c));
}
}
return 0;
}