题意:
大学班级选班长,N 个同学均可以发表意见,若意见为 A B 则表示 A 认为 B 合适,意见具有传递性,即 A 认为 B 合适,B 认为 C 合适,则 A 也认为 C 合适 。现在收集了M条意见,想要知道最高票数,并给出一份候选人名单,即所有得票最多的同学。
输入:
本题有多组数据。第一行 T 表示数据组数。每组数据开始有两个整数 N 和 M (2 <= n <= 5000, 0 <m <= 30000),接下来有 M 行包含两个整数 A 和 B(A != B) 表示 A 认为 B 合适。
输出:
对于每组数据,第一行输出 “Case x: ”,x 表示数据的编号,从1开始,紧跟着是最高的票数。 接下来一行输出得票最多的同学的编号,用空格隔开,注意不忽略行末空格。
输入样例:
2
4 3
3 2
2 0
2 1
3 3
1 0
2 1
0 2
输出样例:
Case 1: 2
0 1
Case 2: 2
0 1 2
解题思路:
对于可以转化为有向图的问题来说,我们通常考虑求出scc并缩点,即将互相可达与单向可达分开来进行考虑。缩点之后,不难发现对于属于第 i 个 SCC 的点来说,答案分为两部分,令 SCC[i] 表示第 i 个 SCC 中点的个数,对于当前 SCC 中的点,得到的票数 ans += SCC[i] – 1(去除自己) ,还有一部分票数是其它 SCC 中的点SUM ( SCC[j] ),其中 j 可到达 i,容易发现最后得票最高的人一定出现在出度为 0 的 SCC当中。因为如果某个scci中的人得票最高,而scci出度不为零,假设scci指向sccj,则给scci投票的人一定也会投票给sccj,而且scci中每一个人都可以投票给sccj,这里票数就多了一,加上还有scc[j]-1(该scc自己)以及其他可能的给sccj中的人投票的人,所以scci中的人一定不可能得票最高。因此我们将边反向,对每个入度为 0 的点进行 dfs,计算其所能够到达的点的 SUM(SCC[j]),即可得到答案。
注意事项:
1、计算scc以后把每个scc看成一个点,此时图中不可能再存在环,否则就不是scc了,不满足最大连通子图的概念,只是连通子图而已。
2、图中的边是否反向并不影响scc的划分,所以在原图G1或者反图G2上求scc都可以。
总结:
一道综合性很强的题目,涉及到scc、缩图、排序等等算法,需要对每一个小的方法都掌握好,注意图的数据结构的选择以及不同的dfs函数的不同,vis数组可以共用同一个或者使用其他也具有vis功能的数组(如这道题中的数组c),注意对于多组数据的题目需要在每次计算前进行初始化,同时注意一下数据的输出格式。
参考代码:
#include <iostream>
#include <queue>
#include <stack>
#include <vector>
#include <algorithm>
using namespace std;
int n,m;
int mymax=0;
const int N=5010;
int c[N],dfn[N],vis[N],dcnt,scnt;
int scc[N];
int in_degree[N];
vector<int> G1[N],G2[N],G3[N];//原图 反图 缩点后的图
struct scc{
vector<int> points;
int value;//票数
} p[N];//scc
void ini(){
for (int i=1; i<=n; i++) {
p[i].value=0;
p[i].points.clear();
}
for (int i=0; i<n; i++) {
G1[i].clear();G2[i].clear();G3[i].clear();
}
}
int v;//记录票数
void dfs1(int x){
vis[x] = 1;
for(int i=0;i<G1[x].size();i++){
int y=G1[x][i];
if (!vis[y]) dfs1(y);}
dfn [dcnt] = x;
dcnt++;
}
void dfs2(int x)
{
c[x]=scnt;
for(int i=0; i<G2[x].size(); i++){
int y=G2[x][i];
if(!c[y])
dfs2(G2[x][i]);
}
}
void kosaraju() //求SCC
{
dcnt=0;scnt=0;
memset(vis, 0, sizeof(vis));
memset(c, 0, sizeof(c));
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]);}
}
void add(int x,int y){
G1[x].push_back(y);
G2[y].push_back(x);
}
void dfs3(int x){//x:scc
vis[x]=1;
for(int i=0;i<G3[x].size();i++){
int y=G3[x][i];
if (!vis[y]) {
v+=scc[y];
dfs3(y);}
}
}
int main(int argc, const char * argv[]) {
int t;cin>>t;
int ca=1;//case
while (t--) {
vector<int> ans;//记录票数最高的人们
ans.clear();
mymax=0;
memset(vis, 0, sizeof(vis));
memset(scc, 0, sizeof(scc));
memset(in_degree, 0, sizeof(in_degree));
cin>>n>>m;
ini();
while (m--) {
int x,y;cin>>x>>y;
add(x, y);
}
kosaraju();//求scc
for (int i=0; i<n; i++) {
scc[c[i]]++;
}
for (int i=0;i<n;i++) {
p[c[i]].points.push_back(i);
p[c[i]].value=scc[c[i]]-1;
}
//缩点
for (int x=0; x<n; x++) {
for(int i=0;i<G2[x].size();i++){
int y=G2[x][i];
if(c[x]==c[y])continue;
G3[c[x]].push_back(c[y]);
in_degree[c[y]]++;
}
}
for(int i=1;i<=scnt;i++){
if(in_degree[i]==0){
memset(vis, 0, sizeof(vis));
v=0;dfs3(i);
p[i].value+=v;
if(p[i].value>mymax)mymax=p[i].value;
}//vis:连通分支是否vis过
}
for (int i=1; i<=scnt; i++) {
if(p[i].value==mymax){
for (int j=0; j<p[i].points.size(); j++) {
ans.push_back(p[i].points[j]);
}
}
}
sort(ans.begin(), ans.end());
cout<<"Case "<<ca<<": "<<mymax<<endl;
ca++;
for (int i=0; i<ans.size()-1; i++) {
cout<<ans[i]<<" ";
}
cout<<ans[ans.size()-1];
cout<<endl;
}
return 0;
}