题目描述
大学班级选班长,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;
}