CSP-强联通分量和Kosaraju实现

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;
}

猜你喜欢

转载自blog.csdn.net/qq_43942251/article/details/105546029