• 最大半联通子图(Tarjan)


    一个有向图 G=(V,E) 称为半连通的 (Semi-Connected),如果满足:∀u,v∈V,满足 u→v 或 v→u,即对于图中任意两点 u,v,存在一条 u 到 v 的有向路径或者从 v 到 u 的有向路径。

    若 G′=(V′,E′) 满足,E′ 是 E 中所有和 V′ 有关的边,则称 G′ 是 G 的一个导出子图。

    若 G′ 是 G 的导出子图,且 G′ 半连通,则称 G′ 为 G 的半连通子图。

    若 G′ 是 G 所有半连通子图中包含节点数最多的,则称 G′ 是 G 的最大半连通子图。

    给定一个有向图 G,请求出 G 的最大半连通子图拥有的节点数 K,以及不同的最大半连通子图的数目 C。

    由于 C 可能比较大,仅要求输出 C 对 X 的余数。

    输入格式
    第一行包含三个整数 N,M,X。N,M 分别表示图 G 的点数与边数,X 的意义如上文所述;

    接下来 M 行,每行两个正整数 a,b,表示一条有向边 (a,b)。

    图中的每个点将编号为 1 到 N,保证输入中同一个 (a,b) 不会出现两次。

    输出格式
    应包含两行。

    第一行包含一个整数 K,第二行包含整数 C mod X。

    数据范围
    1≤N≤105,
    1≤M≤106,
    1≤X≤108
    输入样例:
    6 6 20070603
    1 2
    2 1
    1 3
    2 4
    5 6
    6 4
    输出样例:
    3
    3

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    #include <unordered_set>
    
    using namespace std;
    
    typedef long long LL;
    
    const int N = 100010, M = 2000010;
    
    int n, m, mod;
    int h[N], hs[N], e[M], ne[M], idx;
    int dfn[N], low[N], timestamp;
    int stk[N], top;
    bool in_stk[N];
    int id[N], scc_cnt, scc_size[N];
    int f[N], g[N];
    // 有新图所以传h
    void add(int h[],int a,int b)
    {
        e[idx] = b,ne[idx] = h[a],h[a] = idx++;
    }
    
    void tarjan(int u)
    {
        dfn[u] = low[u] = ++timestamp;
        stk[++top] = u,in_stk[u] = true;
        for(int i =h[u];~i;i=ne[i])
        {
            int j = e[i];
            // 树枝边
            if(!dfn[j])
            {
                tarjan(j);
                low[u] = min(low[u],low[j]);
            }
            // 横向边
            else if(in_stk[j])low[u] = min(low[u],dfn[j]);
        }
        // 当前分量最高点 把当前点取出来作为一个强连通分量
        // ⭐
        // 解释一下为什么tarjan完是逆dfs序
        // 假设这里是最高的根节点fa
        // 上面几行中 fa的儿子节点j都已经在它们的递归中走完了下面9行代码
        // 其中就包括 ++scc_cnt 
        // 即递归到高层节点的时候 子节点的scc都求完了
        // 节点越高 scc_id越大
        // 在我们后面想求链路dp的时候又得从更高层往下
        // 所以得for(int i=scc_cnt(根节点所在的scc);i;i--)开始
        if(dfn[u]==low[u])
        {
            ++scc_cnt;
            int y;
            do{//由于是dfs搜到一层stk.push 所以stk最先pop出来的是最深层的
                y=stk[top--];
                in_stk[y] = false;
                // id[y] = scc_cnt 属于第scc_cnt个强连通分量
                id[y] = scc_cnt;
                scc_size[scc_cnt]++;
            }while(y!=u);
        }
    }
    
    int main()
    {
        memset(h,-1,sizeof h);
        memset(hs,-1,sizeof hs);
        cin >> n >> m >> mod;
        while(m--)
        {
            int a,b;
            cin >> a >> b;
            add(h,a,b);
        }
        // tarjan算强连通分量
        for(int i = 1;i<=n;i++)
        {
            if(!dfn[i])
            {
                tarjan(i);
            }
        }
        unordered_set<LL> S;// 边是(u,v) hash后 u*1000000+v
        for(int i=1;i<=n;i++)
        {
            for(int j = h[i];~j;j=ne[j])
            {
                int k = e[j];
                int a = id[i],b= id[k];
                // 边判重
                LL hash = a*1000000ll+b;
                // 如果a和b不在一个强连通分量 且 边(a,b)没被加过
                if(a!=b && !S.count(hash))
                {
                    add(hs,a,b);
                    S.insert(hash);
                }
            }
        }
        // 强连通分量算完后
        // 拓扑序一定是按照节点编号递减的顺序->不需要重新拓扑排序了
        // 在这个拓扑序上求最长路
        for(int i=scc_cnt;i;i--)
        {
            // !f[i] i没被更新过 i为一条链的起点
            if(!f[i])
            { 
                f[i] = scc_size[i];
                g[i] = 1;
            }
            for(int j = hs[i];~j;j=ne[j])
            {
                int k = e[j];
                if(f[k]<f[i]+scc_size[k])
                {
                    f[k] = f[i]+scc_size[k];
                    g[k] = g[i];
                }
                else if(f[k]==f[i]+scc_size[k])
                {
                    g[k] = (g[k]+g[i])%mod;
                }
            }
        }
    
        int maxf = 0,sum = 0;
        // 对比每一条路终点
        for(int i = 1;i<=scc_cnt;i++)
        {
            if(f[i]>maxf)
            {
                maxf = f[i];
                sum = g[i];
            }
            else if(f[i] == maxf)sum=(sum+g[i])%mod;
        }
        cout << maxf << endl;
        cout << sum;
        return 0;
    }
    
    
    
  • 相关阅读:
    软件测试工程师的素质
    软件测试阶段的划分
    如何去涉及测试用例
    如何判断测试结束
    Linux常用命令大全
    测试用例设计方法
    Web测试方法
    loadrunner
    谈谈重复性测试
    软件测试思维导图
  • 原文地址:https://www.cnblogs.com/wk-love-zsy/p/14003977.html
Copyright © 2020-2023  润新知