SCC
SCC就是强连通分量,是有向图中的一个概念
**强连通:**有向图G中任意两个结点联通
**强连通分量:**极大的强连通子图
Kosaraju算法
算法的目标就是找到有向图中所有的SCC
- 先dfs确定原图的逆后序序列
- 再dfs在反图中(边的方向全部改变)按照逆后序序列进行遍历,寻找出所有的SCC
原理是原图和反图具有一样的SCC
班长竞选
大学班级选班长,N 个同学均可以发表意见
若意见为 A B 则表示 A 认为 B 合适,意见具有传递性,即 A 认为 B 合适,B 认为 C 合适,则 A 也认为 C 合适
现在收集了M条意见,想要知道最高票数,需要给出一份候选人名单,即所有得票最多的同学。
本题有多组数据。第一行 T 表示数据组数。每组数据开始有两个整数 N 和 M (2 <= n <= 5000, 0 <m <= 30000),接下来有 M 行包含两个整数 A 和 B(A != B) 表示 A 认为 B 合适
对于每组数据,第一行输出 “Case x: ”,x 表示数据的编号,从1开始,紧跟着是最高的票数。
接下来一行输出得票最多的同学的编号,用空格隔开,不忽略行末空格!
sample input:
2
4 3
3 2
2 0
2 1
3 3
1 0
2 1
0 2
sample output:
Case 1: 2
0 1
Case 2: 2
0 1 2
思路:
- 将每一个人看成是一个点,利用支持关系可以构成一张有向图。由于观点具有传递性,所以如果一个SCC中的某个人支持了SCC外的一个人A,那么相当于整个SCC中的人都支持A
- 所以本题可以使用Kosarju算法来首先找到SCC,对于反图来说,答案肯定存在于入度为0的SCC中
- 遍历图中所有边,当一条边的两个端点不存在于同一个SCC中的时候,说明这条边就是两个SCC之间的连接边,通过边的关系可以计算每一个SCC的出度(也就是反图的入度)
- 首先遍历所有点找到入度为0的SCC,确定答案存在于这个SCC中,再次遍历这个SCC能到达的所有SCC,将能遍历到的SCC的点数都加入到答案中
- 再次遍历选定的SCC,加上SCC中点的个数-1(个人不能投自己)
- 需要注意输出的格式
代码:
-这是第一版的代码,但是只能过c++编译器,在g++上运行会出现TLE的问题。发掘应该是点和边的数组都默认使用了最大值3e5来开,浪费了时间
#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>
#include<cmath>
using namespace std;
const int MN=3e5+1;
int N,M;
struct Edge
{
int u,v,next;
}e1[MN],e2[MN];//正图反图
int head1[MN],head2[MN],dfn[MN];
int scnt,c[MN],scc[MN],indeg[MN];//scnt连通子图数量,c[i]第i号处于c[i]号SCC中
int dcnt,tot;
//vis-子图中的点是否被访问到,visit- SCC是否被访问到
bool vis[MN],visit[MN];
int ans[MN];
void add(int u,int v)//tot- 图中边的总数量
{
e1[tot].u=u;
e1[tot].v=v;
e1[tot].next=head1[u];
head1[u]=tot;
e2[tot].u=v;
e2[tot].v=u;
e2[tot].next=head2[v];
head2[v]=tot;
tot++;
}
void dfs1(int x)
{
vis[x]=true;
for(int i=head1[x];i!=-1;i=e1[i].next)
{
if(!vis[e1[i].v])
dfs1(e1[i].v);
}
dfn[dcnt++]=x;
}
void dfs2(int x)
{
c[x]=scnt;
scc[scnt]++;
for(int i=head2[x];i!=-1;i=e2[i].next)
{
if(!c[e2[i].v])
dfs2(e2[i].v);
}
}
void dfs3(int x,int j)
{
vis[x]=true;
if(!visit[c[x]])
ans[j]+= scc[c[x]];
visit[c[x]]=true;
for(int i=head2[x];i!=-1;i=e2[i].next)//开始找SCC中所有点
{
if(!vis[e2[i].v])
dfs3(e2[i].v,j);
}
}
void kosaraju()
{
for(int i=0;i<N;i++)
if(!vis[i]) dfs1(i);
for(int i=N-1;i>=0;i--)
{
if(!c[dfn[i]])
{
scnt++;
dfs2(dfn[i]);
}
}
}
int main(){
int T;
scanf("%d",&T);
for(int i=1;i<=T;i++)
{
tot=0;
memset(head1,-1,sizeof(head1));
memset(head2,-1,sizeof(head2));
scanf("%d%d",&N,&M);
while(M--)
{
int a,b;
scanf("%d%d",&a,&b);
add(a,b);
}
printf("Case %d:",i);
dcnt=scnt=0;
memset(scc,0,sizeof(scc));
memset(vis,false,sizeof(vis));
memset(c,0,sizeof(c));
memset(indeg,0,sizeof(indeg));
memset(ans,-1,sizeof(ans));
kosaraju();
for(int i=0;i<M;i++)
{
int tu=e1[i].u,tv=e1[i].v;//原图中的起点和终点
if(c[tu]!=c[tv])//所处连通子图不同->边是连接边
indeg[c[tu]]++; //对应原图的起点是返图的终点,indeg++
}
int j=0,tic=-1;
for(int i=0;i<N;i++)
{
j=c[i];//开始找SCC
if(indeg[j]==0)//入度为零的点就是答案
{
memset(vis,false,sizeof(vis));
memset(visit,false,sizeof(visit));
dfs3(i,j); //点,所属scc
indeg[j]=1;
tic=max(tic,ans[j]);
}
}
printf(" %d\n",tic);
int flag=0;
for(int i=0;i<N;++i)
{
if(ans[c[i]]==tic)
{
if(flag==0)
{
printf("%d",i);
flag++;
}
else printf(" %d",i);
}
}
printf("\n");
}
return 0;
}
- 对于点的最大范围进行重置-NN,成功使用g++ AC
#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>
#include<cmath>
using namespace std;
const int NN=5001;
const int MN=3e5+1;
int N,M;
struct Edge
{
int u,v,next;
}e1[MN],e2[MN];//正图反图
int head1[NN],head2[NN],dfn[NN];
int scnt,c[NN],scc[NN],indeg[NN];//scnt连通子图数量,c[i]第i号处于c[i]号SCC中
int dcnt,tot;
//vis-子图中的点是否被访问到,visit- SCC是否被访问到
bool vis[NN],visit[NN];
int ans[NN];
void add(int u,int v)//tot- 图中边的总数量
{
e1[tot].u=u;
e1[tot].v=v;
e1[tot].next=head1[u];
head1[u]=tot;
e2[tot].u=v;
e2[tot].v=u;
e2[tot].next=head2[v];
head2[v]=tot;
tot++;
}
void dfs1(int x)
{
vis[x]=true;
for(int i=head1[x];i!=-1;i=e1[i].next)
{
if(!vis[e1[i].v])
dfs1(e1[i].v);
}
dfn[dcnt++]=x;
}
void dfs2(int x)
{
c[x]=scnt;
scc[scnt]++;
for(int i=head2[x];i!=-1;i=e2[i].next)
{
if(!c[e2[i].v])
dfs2(e2[i].v);
}
}
void dfs3(int x,int j)
{
vis[x]=true;
if(!visit[c[x]])
ans[j]+= scc[c[x]];
visit[c[x]]=true;
for(int i=head2[x];i!=-1;i=e2[i].next)//开始找SCC中所有点
{
if(!vis[e2[i].v])
dfs3(e2[i].v,j);
}
}
void kosaraju()
{
for(int i=0;i<N;i++)
if(!vis[i]) dfs1(i);
for(int i=N-1;i>=0;i--)
{
if(!c[dfn[i]])
{
scnt++;
dfs2(dfn[i]);
}
}
}
int main(){
int T;
scanf("%d",&T);
for(int i=1;i<=T;i++)
{
tot=0;
memset(head1,-1,sizeof(head1));
memset(head2,-1,sizeof(head2));
scanf("%d%d",&N,&M);
while(M--)
{
int a,b;
scanf("%d%d",&a,&b);
add(a,b);
}
printf("Case %d:",i);
dcnt=scnt=0;
memset(scc,0,sizeof(scc));
memset(vis,false,sizeof(vis));
memset(c,0,sizeof(c));
memset(indeg,0,sizeof(indeg));
memset(ans,-1,sizeof(ans));
kosaraju();
for(int i=0;i<M;i++)
{
int tu=e1[i].u,tv=e1[i].v;//原图中的起点和终点
if(c[tu]!=c[tv])//所处连通子图不同->边是连接边
indeg[c[tu]]++; //对应原图的起点是返图的终点,indeg++
}
int j=0,tic=-1;
for(int i=0;i<N;i++)
{
j=c[i];//开始找SCC
if(indeg[j]==0)//入度为零的点就是答案
{
memset(vis,false,sizeof(vis));
memset(visit,false,sizeof(visit));
dfs3(i,j); //点,所属scc
indeg[j]=1;
tic=max(tic,ans[j]);
}
}
printf(" %d\n",tic);
int flag=0;
for(int i=0;i<N;++i)
{
if(ans[c[i]]==tic)
{
if(flag==0)
{
printf("%d",i);
flag++;
}
else printf(" %d",i);
}
}
printf("\n");
}
return 0;
}