SDU_week8_C - 班长竞选(Kosaraju求SCC+缩点)

题目描述

大学班级选班长,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 合适。

Output
对于每组数据,第一行输出 “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

题目分析

本题是给定带正环的无权有向图,求某点最大累积票数(即投票可传递)。由于是无权图,使用vector< int >G[MAXN]存图显得尤为方便(但是会比前向星慢一点)。
思路:求出 SCC 并缩点,即将互相可达与单向可达分开考虑。缩点后,不难发现对于属于第 i 个 SCC 的点来说( 令 SCC[i] 表示第 i 个 SCC 中点的个数),答案分为两部分:

  1. 当前 SCC 中的点,ans += SCC[i] – 1(去除自己)
  2. 其它 SCC 中的点 ,SUM ( SCC[j] ),其中 j 可到达 i

然后可以发现最后答案一定出现在原图出度为 0 的 SCC 中,即用反图缩点图跑dfs,记录最大值所在的点即可。

具体操作

首先存好正图和反图,然后加载kosaraju算法计算SCC(强连通分量):从0号点开始,第一遍dfs求DFS后序;然后按照DFS逆后序顺序,第二遍dfs求SCC,此时SCC[MAXN]中存储的是i号点所在的SCC编号。
然后进行缩点操作:缩点操作要进行两次,使用原图缩点图判断出度是否为0,使用反图缩点图进行投票传递计算DFS。
之后遍历反图缩点图中每个入度为0的点进行bfs,同时记录最大值,记得答案要-1,除去自己给自己的投票。
最后循环输出答案即可。

注意

  1. 可能有多个SCC答案相同!都要记录!
  2. 本题输入量较大,使用以下三行关同步:
    ios::sync_with_stdio(false);//不要忘记ios::
    cin.tie(0);
    cout.tie(0);

代码

#define _CRT_SECURE_NO_WARNINGS
#define _ ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);

#include <bits/stdc++.h>
//#include <iostream>
//#include<algorithm>

using namespace std;
int T, N, M;
vector<int>G1[5009];//原图
vector<int>G2[5009];//反图
vector<int>G3[5009];//原图缩点图
vector<int>G4[5009];//反图缩点图
//int zeroDegSCC[5009];//出度为0的SCC号
int SCCcnt[5009];//SCCcnt[i]表示第i个SCC中点的个数
int temp,maxAns, ans[5009];//临时答案数,最大答案数,答案数组

int dcnt;//DFS计数
bool vis[5009];//DFS后序记录是否访问过
int dfsBack[5009];//arr[i]:DFS后序第i个对应的点
void dfs1(int x)
{//求DFS后序
	vis[x] = 1;
	for (auto y : G1[x])
		if (!vis[y])dfs1(y);
	dfsBack[++dcnt] = x;
}

int scnt;//SCC计数
int SCC[5009];//SCC[i]:i号点所在的SCC编号
void dfs2(int x)
{//求SCC
	SCC[x] = scnt;
	for (auto y : G2[x])
		if (!SCC[y])dfs2(y);
}

void kosaraju()
{
	dcnt = scnt = 0;//最后结果都是从1开始
	memset(dfsBack, 0, sizeof(dfsBack));
	memset(SCC, 0, sizeof(SCC));
	memset(vis, 0, sizeof(vis));

	for (int i = 0; i < N; i++)
		if (!vis[i])dfs1(i);

	for (int i = N; i >= 1; i--)
	{
		int u = dfsBack[i];
		if (!SCC[u])++scnt, dfs2(u);
	}
}

void compact()
{//缩点
	for (int x = 0; x < N; x++)
		for (auto y : G1[x])
		{
			if (SCC[x] == SCC[y])continue;//属于同一个SCC,不用管
			int u = SCC[x];
			int v = SCC[y];
			G3[u].push_back(v);//不属于同一个SCC,使其构造新图
		}

	for(int x=0;x<N;x++)
		for (auto y : G2[x])
		{
			if (SCC[x] == SCC[y])continue;//属于同一个SCC,不用管
			int u = SCC[x];
			int v = SCC[y];
			G4[u].push_back(v);//不属于同一个SCC,使其构造新图
		}
}

bool vis2[5009];
void dfs3(int x)
{//计算某个SCC能到达的SCC之和
	temp += SCCcnt[x];
	vis2[x] = 1;
	for (auto y : G4[x])
		if (!vis2[y])dfs3(y);

}

int main()
{
	_;
	int A, B;
    cin >> T;
	for (int i = 1; i <= T; i++)
	{
		memset(G1, 0, sizeof(G1));
		memset(G2, 0, sizeof(G2));
		memset(G3, 0, sizeof(G3));
		memset(G4, 0, sizeof(G4));
		memset(SCCcnt, 0, sizeof(SCCcnt));
		memset(ans, 0, sizeof(ans));

		cin >> N >> M;
		for (int i = 1; i <=M; i++)
		{
			cin >> A >> B;
			G1[A].push_back(B);
			G2[B].push_back(A);
		}

		kosaraju();
		//for (auto a : dfsBack)cout << a << ' ';
		//cout << endl;
		//for (auto a : SCC)cout << a << ' ';
		//cout << endl;
		compact();

		for (int i = 0; i < N; i++)
			SCCcnt[SCC[i]]++;
		//for (auto a : SCCcnt)cout << a << ' ';
		//cout << endl;
		temp = maxAns = 0;
		for (int i = 1; i <= scnt; i++)
		{
			if (G3[i].empty())
			{//某个SCC出度为0,答案一定在原图出度为0,即反图入度为0
			//使用原图缩点图判断出度是否为0,使用反图缩点图DFS
				memset(vis2, 0, sizeof(vis2));
				temp = 0;
				//temp += SCCcnt[i] - 1;//去除自己
				dfs3(i);
				temp -= 1;//去除自己,第一次dfs3已经加上了自己
				//if (temp > ans)ans = temp, SCCnum = i;  可能有多个SCC答案相同!都要记录!
				ans[i] = temp;
				if (temp > maxAns)maxAns = temp;
			}
		}
		//for (int k = 1; k <= scnt; k++)cout << ans[k]<<endl;
		//cout <<"ans"<< ans<<"SCCnum"<<SCCnum << endl;

		vector<int> stu;//最高票学生集合
		for (int k=1;k<=scnt;k++)
		{
			if (ans[k] == maxAns)
			{
				for (int i = 0; i < N; i++)
					if (SCC[i] == k)stu.push_back(i);
			}
		}
		sort(stu.begin(), stu.end());
		cout << "Case " << i << ": " << maxAns << endl;
		for (int i = 0; i < stu.size(); i++)
		{
			if (i == 0)cout << stu[0];
			else cout << ' ' << stu[i];
		}
		cout << endl;

	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值