强连通分支问题

题意:
大学班级选班长,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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值