[置顶] 星球联盟
【问题描述】
在遥远的 S 星系中一共有 N 个星球,编号为 1…N。其中的一些星球决定组成联盟,
以方便相互间的交流。
但是,组成联盟的首要条件就是交通条件。初始时,在这 N 个星球间有 M 条太空
隧道。每条太空隧道连接两个星球,使得它们能够相互到达。若两个星球属于同一个联
盟,则必须存在一条环形线路经过这两个星球,即两个星球间存在两条没有公共隧道的
路径。
为了壮大联盟的队伍,这些星球将建设 P 条新的太空隧道。这 P 条新隧道将按顺序
依次建成。一条新轨道建成后,可能会使一些星球属于同一个联盟。你的任务是计算出,
在一条新隧道建设完毕后,判断这条新轨道连接的两个星球是否属于同一个联盟,如果
属于同一个联盟就计算出这个联盟中有多少个星球。
【输入格式】
第 1 行三个整数 N,M 和 P,分别表示总星球数,初始时太空隧道的数目和即将建
设的轨道数目。
第 2 至第 M+1 行,每行两个整数,表示初始时的每条太空隧道连接的两个星球编
号。
第 M+2 行至第 M+P+1 行,每行两个整数,表示新建的太空隧道连接的两个星球编
号。这些太空隧道按照输入的顺序依次建成。
【输出格式】
输出共 P 行。如果这条新的太空隧道连接的两个星球属于同一个联盟,就输出一个
整数,表示这两个星球所在联盟的星球数。如果这条新的太空隧道连接的两个星球不属
于同一个联盟,就输出”No”(不含引号)。
【样例 1】
alliance.in
3 2 1
1 2
1 3
2 3
alliance.out
3
【样例 2】
alliance.in
5 3 4
1 2
4 3
4 5
2 3
1 3
4 5
2 4
alliance.out
No
3
2
5
【数据范围】
对于 10%的数据有 1≤N,M,P≤100;
对于 40%的数据有 1≤N,M,P≤2000;
对于 100%的数据有 1≤N,M,P≤200000。
【题解】
算法一:暴力枚举
对于每一个询问我们可以枚举每一条边,去掉后判断点对之间的连通性即可。
时间复杂度:O(N(M+P)2)
期望得分:10 分
算法二:朴素的强连通分量
很明显对于每一个询问,我们可以计算当前图的强连通分量,加以判断。使用时间复杂度均为 O(N) kosaraju 或 tarjan 算法。
时间复杂度:O(N(M+P))
期望得分:40 分
算法三:模型的转换与多种图论算法的综合运用
每一次都计算强连通分量显然不合适,这样会连带计算出很多我们不需要的连通分量,我们要做的就是对新加的边的两个端点进行分析。很明显,如果这两个点已经处于同一个强连通分量中,就不必改变,如果这两个点不在同一个强连通分量中,可能有两种情况:
1、这条边使得那两个点处于同一强连通分量中。
2、这条边使得两个点联通,即当前通的一个桥。
对于第二种情况,可以预先处理,然而对于第一种情况又该如何处理呢?很明显,如果这条边使得原本不在同一强连通分量的两个点处在了统一的强连通分量,则在添加这条边之前这两个点之间有且仅有唯一一条路径相连。这让我们想起了一种特殊的图——树!树中任意的两个节点之间有且仅有唯一一条路径相连。那么如何将原图转化为一棵树呢?
实际上我们可以将上面所说的情况 2 的边看作为一条树边,很明显一个包含N 个节点的图最多含有 N-1 个桥。将这些桥预先计算出来后,我们可以构造一个树(或森林)。对于一棵树,若树上的两个点不是父子关系,而它们之间新添加了一条边,那么这条边可能形成新的强连通分量,由树的性质,添加过新的边之后,这两个点和这两个点所在路径上所有的点一定处于一个强连通分量之中。对于给定的两个节点,我们可以先找到其最近公共祖先(lca),在将 lca 到这两个点的路径上所有的点合并为同一个强连通分量。这里可以用并查集维护。初始时每个节点指向自己,在合并时,将指针指向所合并的 lca。如上图,当我们新添加红色标出的那条边时,可以将所有绿色的点指向 lca,也就是蓝色的点。在维护并查集时,要注意并查集元素大小的累加。接下来的问题就是如何找到两个点的最近公共祖先呢?最近公共祖先有一个特点:它在树中的高度 H 不大于所要查询的那两个节点在树中的高度。我们可以迭代完成查询 lca 的过程。例如:给定两个点 X,Y,我们得到这两个点在树中的高度,接下来如果 Hx>=Hy 我们就将 X 替换为它的父亲(这里的父亲指缩点后的父亲),否则就将 Y 替换为它的父亲,直到 X=Y 为止。这期间,我们可以顺便完成并查集的合并操作。
显然,一开始我们还需进行对每个节点 H 的计算,这里推荐用 BFS(DFS
可能会爆栈)更为合适。需要注意的是,给定的图未必联通,所以构成的可能不
是树而是森林。
下面让我们总结这个算法的流程:
第一步:依据输入数据构造出树或森林,这里可以用强连通分量找桥也可以选用并查集。
第二步:BFS 计算出每个节点 H 的大小。
第三步:依次处理新加的 P 条边,维护并查集完成 lca 查询及缩点操作。
时间复杂度:O(N+M+P)
期望得分:100 分。
涉及内容: 1.图与树的转化
2.并查集、强连通分量算法
3.宽度优先搜索
4.数据的预处理与再处理。
#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<vector>
#define ll long long
#define re register
#define il inline
#define fp(i,a,b) for(re int i=a;i<=b;i++)
#define fq(i,a,b) for(re int i=a;i>=b;i--)
using namespace std;
const int MAX=200005;
struct Edge
{
int to,next;
}a[MAX<<1];
struct solve
{
int x,y;
}put[MAX],que[MAX];
int size[MAX],deep[MAX],f[MAX];
int head[MAX],tot,Fa[MAX];
int n,m,q,cnt,qcnt,num[MAX];
il void add(int u,int v)
{
a[++tot]=(Edge){v,head[u]};head[u]=tot;
a[++tot]=(Edge){u,head[v]};head[v]=tot;
}
il int find(int x)
{
return (f[x]==x)?f[x]:(f[x]=find(f[x]));
}
il void dfs(int u,int fa)
{
deep[u]=deep[fa]+1;
for(int i=head[u];i;i=a[i].next)
{
int v=a[i].to;
if(v!=fa) Fa[a[i].to]=u,dfs(v,u);
}
}
void Union(int u,int v)
{
while(u!=v)
{
if(deep[u]<deep[v]) swap(u,v);
int x=find(u),y=find(Fa[u]);//最后问题出在这里,Fa[u]打成了f[u]f
if(x!=y) {f[x]=y,size[y]+=size[x];}
u=y;
}
}
il int gi()
{
re int x=0;
re short int t=1;
re char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if(ch=='-') t=-1,ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar();
return x*t;
}
int main()
{
freopen("alliance.in","r",stdin);
freopen("alliance.out","w",stdout);
n=gi(),m=gi(),q=gi();
fp(i,1,n) f[i]=i;
fp(i,1,m)
{
int u=gi(),v=gi();
int x=find(u),y=find(v);
if(x!=y) f[y]=x,add(u,v);
else put[++cnt].x=u,put[cnt].y=v;
}
fp(i,1,q)
{
int u=gi(),v=gi();
int x=find(u),y=find(v);
if(x!=y) f[y]=x,add(u,v);
else que[++qcnt].x=u,que[qcnt].y=v,num[i]=qcnt;
}
fp(i,2,n)
{
int x=find(i),y=find(1);
if(x!=y) add(i,1),f[x]=y;
}
fp(i,1,n) f[i]=i,size[i]=1;
dfs(1,0);
fp(i,1,cnt)
Union(put[i].x,put[i].y);
fp(i,1,q)
if(!num[i]) printf("No\n");
else
{
Union(que[num[i]].x,que[num[i]].y);
printf("%d\n",size[find(que[num[i]].x)]);
}
fclose(stdin);
fclose(stdout);
return 0;
}