CSP-强联通分量相关问题和缩点
基础知识
强联通分量是图论的常见概念之一,强联通问题也是图论算法题中常见的题目。但由于学习强联通分量问题需要有一定的知识基础,我们分成三个部分讲解。
DFS序
DFS是我们十分熟悉的深度优先搜索,但是在深度优先搜索部分并没有谈及DFS序列的相关问题,这里做以补充。DFS序即DFS遍历序列,主要分为两种:
DFS前序:在DFS整个图的过程中,每遍历到一个新点,将该点加入到序列中,得到的结果序列就是DFS前序。
DFS后序:在DFS整个图的过程中,遍历到一个新点后,继续向下遍历,在遍历到边界后,回溯将各点加入序列,得到的结果序列就是DFS后序。
(另一个常用的序列是DFS逆后序,也就是将DFS后序的结果逆向输出)
注意点:在叙述的过程中特意强调了遍历整个图,其含义是图可能不是连通图,从一个点遍历结束后,可能需要换点,直至所有点被遍历完成。
为了方便理解,举出一个例子:
DFS前序:1 2 3 8 4 7 6 5
DFS后序:8 3 2 1 5 6 7 4
DFS逆后序:4 7 6 5 1 2 3 8
DFS序代码(c++)
void dfs(int st)
{
if(vis[st]) return ;
vec_front.push_back(st);//前序序列
vis[st]=1;
for(int i=head[st];i;i=edges[i].next){
dfs1(edges[i].to);
}
vec_back.push_back(st);//后序序列
}
强联通分量
强联通分量的基础定义在此我们也做以赘述,如果你忘记了可以读一遍,如果还清楚可以直接跳过进入具体算法。
强联通是图上的一种性质:如果在一个图内,任意两点A,B之间,都有路径A–>B&&B–>A则认为该图是强联通的。
如果在一个不是强联通的图中,有某些部分满足强联通的特性,则这些部分成为一个图的强联通分量(又称强联通子图)简称SCC。
Kosaraju实现强联通分量划分
为了在一个图中找到所有的SCC,并且标识出来,引入算法Kosaraju来进行实现。
Kosaraju算法的实现过程如下:
1、原图按照点升序求DFS后序,进而获得逆后序。
2、对应原图求出一个反图,在反图上,按照逆后序的点序,DFS整个图。每次遍历到边界时,说明前面的点属于同一个SCC。
(核心思想:将原图的逆后序作为一个拓扑序列,按照这个顺序对反图做DFS)
具体实现可以看文末源代码。
题目简述
大学班级选班长,N 个同学均可以发表意见 若意见为 A B 则表示 A 认为 B 合适,意见具有传递性,即 A 认为 B 合适,B 认为 C 合适,则 A 也认为 C 合适 勤劳的 TT 收集了M条意见,想要知道最高票数,并给出一份候选人名单,即所有得票最多的同学,你能帮帮他吗?
INPUT&输入样例
本题有多组数据。第一行 T 表示数据组数。每组数据开始有两个整数 N 和 M (2 <= n <= 5000, 0 <m <= 30000),接下来有 M 行包含两个整数 A 和 B(A != B) 表示 A 认为 B 合适。
输入样例:
2
4 3
3 2
2 0
2 1
3 3
1 0
2 1
0 2
OUTPUT&输出样例
对于每组数据,第一行输出 “Case x: ”,x 表示数据的编号,从1开始,紧跟着是最高的票数。 接下来一行输出得票最多的同学的编号,用空格隔开,不忽略行末空格!
输出样例:
Case 1: 2
0 1
Case 2: 2
0 1 2
题目重述
同样是经典的传递问题,有A B&&B C–>A C。如果A B,含义是A支持B。求出支持者最多的点和支持的点数(即求出路径入度最大的点)
路径入度:一个点通过直接或者间接可以到A,则A的路径入度+1.
思路概述
在一个随机图中,一定会出现很多SCC(每两点间互相支持),在这种情况下SCC为每个成员都会提供SCC点数-1的路径入度。因此我们可以使用上述的算法,求出图中所有的SCC。但是路径入度还不止如此,因为在SCC求解完成后,整个图变成如下的形态:
如果SCC1有一条指向SCC2的边,则通过SCC的原理可知,SCC1中任意一条边都可以指向SCC2中任意一条边,也就是SCC2中的每个点的路径入度+SCC1的点数。
按照这个原则,我们可以通过SCC后,进行缩点缩边,得到一个如上图所示的有向无环图,再利用该图进行DFS,找到路径入度最大的SCC,该SCC就是所求的点集。
注意点:在SCC形成的有向无环图中,并不需要DFS每个点。可以轻易证明的是,路径入度最大的SCC一定是出度为0的SCC,只需要DFS这些SCC并比较大小即可。
题目源码
#include<iostream>
#include<queue>
#include<algorithm>
#include<vector>
#include<set>
using namespace std;
const int M=5E4+10;
const int inf=1E9;
int head[M],vis[M],head_minus[M],color[M],col_num[M],dis[M],col_dis[M];
int scc_head[M];
int point_cnt,edge_cnt,number1=0,number2=0,group,color_cnt=0,mytot=0;
vector<int> vec;
set<pair<int,int> > st;
struct edge{
int to,next,w;
}edges[M],revr[M],myedge[M],*ped;
void add(int x,int y,int z)
{
int *head_flag,*cnt;
if(z==1){ped=edges,head_flag=head,cnt=&number1;}
else if(z==2){ped=revr,head_flag=head_minus,cnt=&number2;}
else {ped=myedge,head_flag=scc_head,cnt=&mytot;dis[y]++;}
(*cnt)++;
ped[*cnt].to=y;
ped[*cnt].next=head_flag[x],head_flag[x]=*cnt;
}
void init()
{
number1=1,number2=1,color_cnt=0,mytot=0;
st.clear();
vec.clear();
for(int i=0;i<=point_cnt;i++)
{
col_dis[i]=0;
head[i]=0;
scc_head[i]=0;
vis[i]=0;
head_minus[i]=0;
col_num[i]=0;
color[i]=0;
dis[i]=0;
}
}
//正dfs获得逆反序
void dfs1(int st)
{
if(vis[st]) return ;
vis[st]=1;
for(int i=head[st];i;i=edges[i].next){
dfs1(edges[i].to);
}
vec.push_back(st);
}
//逆反序dfs生成scc
void dfs2(int st)
{
color[st]=color_cnt;
for(int i=head_minus[st];i;i=revr[i].next){
int y=revr[i].to;
if(!color[y]) {dfs2(y);++col_num[color_cnt];}
}
}
//生成新图中dfs
int dfs_scc(int st)
{
if(vis[st])
return 0;
int ans=0;
vis[st]=1;
for(int i=scc_head[st];i;i=myedge[i].next)
{
ans+=dfs_scc(myedge[i].to);
}
return ans+col_num[st];
}
void kosaraju()
{
//获得逆后序
for(int i=0;i<point_cnt;++i) dfs1(i);
reverse(vec.begin(),vec.end());//颠倒得到逆后序列
//dfs2求解scc
for(auto it=vec.begin();it!=vec.end();++it)
{
int key=*it;
if(!color[key])
{color_cnt++;col_num[color_cnt]=1;dfs2(key);}
}
//缩点
for(int i=0;i<point_cnt;++i){
for(int j=head[i];j;j=edges[j].next)
{
if(color[i]!=color[edges[j].to])
{
st.insert(make_pair(color[edges[j].to],color[i]));
}
}
}
//缩点加边
for(auto it=st.begin();it!=st.end();it++)
{
add(it->first,it->second,3);
}
//找到最大值
int max_num=-1;
for(int i=1;i<=color_cnt;i++){
if(!dis[i])
{
fill(vis,vis+point_cnt+1,0);
col_dis[i]=dfs_scc(i);
max_num=max(max_num,col_dis[i]);
}
}
printf("%d\n",max_num-1);
//只要col所在的scc值等于max_num,就输出
bool flag=true;
for(int i=0;i<point_cnt;++i)
{
int tmp_scnt=color[i];
if(col_dis[tmp_scnt]==max_num)
{
if(flag)
{
printf("%d",i);
flag=false;
}
else printf(" %d",i);
}
}
}
int main()
{
int start,end;
scanf("%d",&group);
for(int data=1;data<=group;++data)
{
//初始化
scanf("%d %d",&point_cnt,&edge_cnt);
init();
for(int i=0;i<edge_cnt;++i)
{
scanf("%d %d",&start,&end);
add(start,end,1);
add(end,start,2);
}
printf("Case %d: ",data);
kosaraju();
printf("\n");
}
return 0;
}