Week8 作业 C - 班长竞选 SCC Kosaraju HDU - 3639

题目描述
大学班级选班长,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
解题思路

这个有向图中有环,应求出 SCC 并缩点,即将互相可达与单向可达分开考虑。要找到有向图中所有的 SCC,第一遍 dfs 确定原图的逆后序序列,第二遍 dfs 在反图中按照逆后序序列进行遍历。反图即将原图中的有向边反向,每次由起点遍历到的点即构成一个强连通分量。

缩点后,对于属于第 i 个 SCC 的点来说,答案分为两部分,令 SCC[i] 表示第 i 个 SCC 中点的个数,当前 SCC 中的点,ans += SCC[i] – 1 ,而对应其它 SCC 中的点,答案为所以可到达的 SCC 的总和。 这道题还用到了一个结论,即最后答案一定出现在出度为 0 的 SCC中,因此我们将边反向,对每个出度为 0 的点进行 dfs,计算其能到达的点的 SUM(SCC[ j]),即可得到答案。

具体地,我们根据输入数据建正向图 G1 ,反向图 G2 ,跑一遍 DFS ,得到原图的逆后序序列。然后根据这个序列的顺序遍历反图 G2 ,每次遍历的点在一个 SCC 中,在这个过程中,我们把连接两个 SCC 的边保存下来,建立缩点后反向的图 G3 。因为最后答案一定出现在出度为 0 的 SCC 中,我们找到符合条件的 SCC ,进行遍历,最后找出最优 SCC 的集合,以及其对应的点,输出最后的结果。

程序源码
#include<iostream>
#include<vector>
#include<list>
#include<algorithm>
#include<string.h>
using namespace std;

int n, jishu[5005], touch[5005], biaoji[5005];
vector<int> houxu;
vector<int> fenzu[5005];

struct edge { //边结点
	int to;
	int ele;
	edge* next;
	edge() {
		next = NULL;
	}
	edge(int to, int ele, edge* next) :to(to), ele(ele), next(next) {}
};

edge** G1; //正向图
edge** G2; //反向图
edge** G3; //缩点后的反向图
int* G3out;
void setmem() { //重置数据
	memset(touch, 0, sizeof(touch));
	memset(jishu, 0, sizeof(jishu));
	memset(biaoji, 0, sizeof(biaoji));
	houxu.clear();

}

void dfs(int i) { //第一轮DFS标记确定原图的逆后序序列
	if (touch[i]) return;
	touch[i] = 1;
	edge* thisedge = G1[i];
	while (thisedge != NULL) {
		int v = thisedge->to;
		if (!touch[v]) {
			dfs(v);
		}
		thisedge = thisedge->next;
	}
	houxu.push_back(i); //标记
}

void rdfs(int i, int mark) { //第二遍 dfs 在反图中按照逆后序序列进行遍历
	if (touch[i]) return; //每次由起点遍历到的点即构成一个 SCC
	touch[i] = 1;
	biaoji[i] = mark;
	jishu[mark]++; //记录下每个SCC内点的个数
	fenzu[mark].push_back(i);
	edge* thisedge = G2[i];
	while (thisedge != NULL) {
		int v = thisedge->to;
		if (!touch[v]) {
			rdfs(v, mark);
		}
		if (biaoji[v] != mark) { //新增SCC间的连线,反图
			G3[mark] = new edge(biaoji[v], 1, G3[mark]);
			G3out[biaoji[v]]++;
		}
		thisedge = thisedge->next;
	}

}

int dfsrs(int cur, int* tg) { //DFS缩点后的图求结果
	tg[cur] = 1;
	int sum = jishu[cur];
	edge* thisedge = G3[cur];
	while (thisedge != NULL) {
		int v = thisedge->to;
		if (!tg[v]) {
			sum += dfsrs(v, tg);
		}
		thisedge = thisedge->next;
	}
	return sum;
}

int main() {
	int t, m, a, b;
	scanf("%d", &t);
	for (int t1 = 0; t1 < t; t1++) {
		setmem();
		G1 = new edge * [5005]{ NULL }; //初始化
		G2 = new edge * [5005]{ NULL };
		G3 = new edge * [5005]{ NULL };
		scanf("%d%d", &n, &m);
		for (int m1 = 0; m1 < m; m1++) {
			scanf("%d%d", &a, &b);
			G1[a + 1] = new edge(b + 1, 1, G1[a + 1]); //正向图添加边
			G2[b + 1] = new edge(a + 1, 1, G2[b + 1]); //反向图添加边
		}

		for (int i = 1; i <= n; i++) {
			dfs(i);
		}
		memset(touch, 0, sizeof(touch));

		G3out = new int[5005]{ 0 }; //统计入度

		int Group = 0;
		for (int i = houxu.size() - 1; i >= 0; i--) {
			if (!biaoji[houxu[i]]) {
				rdfs(houxu[i], ++Group);
			}
		}

		vector<int> okGroup; //找到出点为0的SCC
		okGroup.clear();
		for (int i = 1; i <= Group; i++) {
			if (G3out[i] == 0) {
				okGroup.push_back(i);
			}
		}

		memset(touch, 0, sizeof(touch));
		vector<int> resultGroup;
		resultGroup.clear();

		int currentMax = -1;
		for (int i = 0; i < okGroup.size(); i++) { //从出点为0的SCC开始遍历缩点后的图,返回其能到达点的的个数
			int tg[5005] = { 0 };
			int theResult = dfsrs(okGroup[i], tg);
			if (theResult > currentMax) { //有更优解
				currentMax = theResult;
				resultGroup.clear();
				resultGroup.push_back(okGroup[i]);
			}
			else if (theResult == currentMax) {
				resultGroup.push_back(okGroup[i]);
			}
		}

		list<int> resultEle;
		resultEle.clear();
		for (int i = 0; i < resultGroup.size(); i++) {
			for (int j = 0; j < fenzu[resultGroup[i]].size(); j++) { //找到某个强连通分量对应的结点,存储
				resultEle.push_back(fenzu[resultGroup[i]][j]);
			}
		}

		resultEle.sort(); //对结果按升序排序

		printf("Case %d: %d\n", t1 + 1, currentMax - 1); //输出结果
		int rsstuc = resultEle.size() - 1;
		for (list<int>::iterator it = resultEle.begin(); it != resultEle.end(); it++) {
			if (rsstuc) {
				printf("%d ", *it - 1);
			}
			else {
				printf("%d\n", *it - 1);
			}
			rsstuc--;
		}
		for (int i = 1; i <= n; i++) { //下面是清除数据
			edge* thisnode = G1[i];
			while (thisnode != NULL) {
				edge* thenext = thisnode->next;
				delete thisnode;
				thisnode = thenext;
			}
		}
		for (int i = 1; i <= n; i++) {
			edge* thisnode = G2[i];
			while (thisnode != NULL) {
				edge* thenext = thisnode->next;
				delete thisnode;
				thisnode = thenext;
			}
		}
		for (int i = 1; i <= n; i++) {
			fenzu[i].clear();
		}
		for (int i = 1; i <= Group; i++) {
			edge* thisnode = G3[i];
			while (thisnode != NULL) {
				edge* thenext = thisnode->next;
				delete thisnode;
				thisnode = thenext;
			}
		}
		delete[] G1; //析构
		delete[] G2;
		delete[] G3;
		delete[] G3out;
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值