【Week8作业 C】班长竞选【SCC缩点】

题意:

大学班级选班长,n个同学均可以发表意见。若意见为A B,则表示A认为B合适。意见具有传递性,即A认为B合适,B认为C合适,则A也认为C合适。共m条意见,要求出最高票数和候选人名单。


思路:

有向图,有强联通分量SCC,应先求出所有SCC并缩点。求SCC用kosaraju算法,第一遍dfs1确定原图的逆后序序列,第二遍dfs2在反图中按照逆后序序列进行遍历,每次由起点遍历到的点即构成一个SCC。
缩点:求完SCC后,此时有scnt个SCC,且c[i]为顶点i所在的SCC编号。结构point里有一个vector,用来存放该SCC里的所有顶点。point P[scnt]就存储了所有SCC的顶点。用vector G3来存放scc图,且边为反向(之后有用)。注意对边(u,v)添加(c[u],c[v])时,要保证c[u]!=c[v],即两个点不在同一个SCC里。同时要注意两个scc顶点间可能会有多条边,要通过vector的unique函数来去重。
缩点后不难发现对于属于第i个SCC的点来说,答案分为两部分,当前SCC中的点除去自己和其他SCC中的点,即ans=SCC[i]-1+sum(SCC[j]),其中j可到达i。稍加思考,可以发现最后答案一定出现在出度为0的SCC中。所以将G3图中的边反向,对每个入度为0的点进行dfs3,计算其能到达的点SUM(SCC[j]),即可得到答案。


总结:

一道综合性很强的题,要求SCC,再缩点,然后dfs,最后答案输出还要略加注意。还应注意这n个同学的编号为0~n-1。


代码:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
//注意n个同学编号为0~n-1 
int n,m,a,b;
vector<int> G1[5010],G2[5010];	//原图与反图
int c[5010],dfn[5010],vis[5010],dcnt,scnt;
//c[i] i号点所在SCC编号;dfn[i] dfs后序列中第i个点
//dcnt dfs序计数;scnt scc计数
void dfs1(int x)
{
	vis[x]=1;
	for(int i=0; i<G1[x].size(); i++)
		if(!vis[G1[x][i]])
			dfs1(G1[x][i]);
	dfn[dcnt]=x;
	dcnt++;
}
void dfs2(int x)
{
	c[x]=scnt;
	for(int i=0; i<G2[x].size(); i++)
		if(!c[G2[x][i]])
			dfs2(G2[x][i]);
}
void kosaraju()	//求SCC
{
	dcnt=0,scnt=0;
	for(int i=0; i<5010; i++)
		c[i]=0,vis[i]=0;
	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]);
}
//scc顶点编号从1开始 
struct point	//scc缩点 
{
	vector<int> allV;
};
point P[5010];	//用于存放scc的点 
vector<int> G3[5010];	//存放scc间的关系,且边为反向 
int indeg[5010];	//入度 	
int dfs3(int v)	//计算入度为0的点的sum(scc[j])
{
	vis[v]=1;
	int ans=P[v].allV.size();
	for(int i=0;i<G3[v].size();i++)
		if(!vis[G3[v][i]])
			ans=ans+dfs3(G3[v][i]);
	return ans;
} 
int main()
{
	ios::sync_with_stdio(false);
	int T;
	cin>>T;
	for(int sjzs=1;sjzs<=T;sjzs++)
	{
		cin>>n>>m;
		//图初始化
		for(int i=0; i<n; i++)
			G1[i].clear(),G2[i].clear();
		//读入图 
		for(int i=0; i<m; i++)
		{
			cin>>a>>b;
			G1[a].push_back(b);
			G2[b].push_back(a);
		}
		//求SCC
		kosaraju();
		//缩点
		int sum[scnt+1];
		for(int i=1;i<=scnt;i++)	
			G3[i].clear(),indeg[i]=0,sum[i]=0,P[i].allV.clear(); 
		for(int i=0;i<n;i++)
		{
			P[c[i]].allV.push_back(i);
			for(int j=0;j<G1[i].size();j++)
			{
				if(c[i]!=c[G1[i][j]])	//两点不在同一个scc 
				{
					G3[c[G1[i][j]]].push_back(c[i]);
					indeg[c[i]]++;
				}
			}
		}
		//G3图应去重 
		for(int i=1;i<=scnt;i++)
		{
			sort(G3[i].begin(),G3[i].end());
			G3[i].erase(unique(G3[i].begin(),G3[i].end()),G3[i].end());
		}
		for(int i=1;i<=scnt;i++)
		{
			if(indeg[i]==0)
			{
				//清空vis
				for(int j=1;j<=scnt;j++)	vis[j]=0;
				sum[i]=dfs3(i)-1;	//自己的点数要-1 
			}
		}
		//输出
		int maxpiao=0;
		for(int i=1;i<=scnt;i++)
			if(maxpiao<sum[i])	maxpiao=sum[i];
		int ans[5010],index=0;
		for(int i=1;i<=scnt;i++)
		{
			if(sum[i]==maxpiao)	//将scci加入到答案序列ans中 
			{
				for(int j=0;j<P[i].allV.size();j++)
				{
					ans[index]=P[i].allV[j];
					index++;
				}
			}
		}
		sort(ans,ans+index); 
		cout<<"Case "<<sjzs<<": "<<maxpiao<<endl;
		for(int i=0;i<index-1;i++)
			cout<<ans[i]<<" ";
		cout<<ans[index-1]<<endl;
	}

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值