AcWing 1175. 最大半连通子图


题目传送门

一、题目描述

相当于是把对强连通分量的连通换成了半联通。
让你求最大半联通图中点的数目和一共有多少个最大半联通子图。

二、解题思路

经过仔细思考我们不难发现,强连通分量必定是半联通的
举个例子:
\(1 —> 2 — > 3 —>1\) 显然是一个强连通分量
我们在该环中不难找到一个最大半联通子图 \(1 – > 2 –> 3\) 或者 \(2 –> 3 –> 1\)
我们又知道,一个图处理完强连通分量后,如果将一个强连通分量看成一个点,那么整个图就是一个拓扑图(\(DAG\)
经过观察发现,处理完后得到的\(DAG\)图上的每一条链都可以看作是一个半联通子图
于是,第一个问题便转化成了在\(DAG\)求最长链中点的数目
第二个问题便转化成了在\(DAG\)上求有多少条最长链,即图中最长链的数目

注意!!还没有完
在转化得到的\(DAG\)图中,每个之间可能连了多条边,这会使我们在求有多少条最长链时出现错误(why??)!!
所以,我们需要借助\(hash\)表来判重!!

三、实现代码

#include 

using namespace std;
typedef long long LL;
const int N = 100010, M = 2000010; // 因为要建新图,两倍的边
int n, m, mod;                     // 点数、边数、取模的数
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];
int h[N], hs[N], e[M], ne[M], idx; // h: 原图;hs: 新图
void add(int h[], int a, int b) {
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

// tarjan求强连通分量
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]);
    }
    if (dfn[u] == low[u]) {
        ++scc_cnt;
        int y;
        do {
            y = stk[top--];
            in_stk[y] = false;
            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;
    for (int i = 0; i < m; i++) {
        int a, b;
        cin >> a >> b;
        add(h, a, b);
    }

    // (1) 求SCC
    for (int i = 1; i <= n; i++)
        if (!dfn[i]) tarjan(i);

    // (2) 缩点,建图
    //对连着两个不同强连通分量的边进行判重
    unordered_set S; // 判断是否为重边 (u, v) -> u * 1000000ll + 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;    //计算DAG中两个点的hash值
            if (a != b && !S.count(hash)) { //去重边
                add(hs, a, b);              //加入到新图
                S.insert(hash);             //记录a,b端点的边使用过了
            }
        }

    // (3) 根据拓扑序遍历DAG,从scc_cnt向前遍历自然满足拓扑序
    //     计算以每个“点”(强连通分量)为终点的最长链中的点的数目f[i]和数量g[i]
    for (int i = scc_cnt; i; i--) {
        // base case 递推起点
        if (!f[i]) { // 说明是入度为0的点
            f[i] = scc_size[i];
            g[i] = 1;
        }
        for (int j = hs[i]; ~j; j = ne[j]) {
            int k = e[j]; // 边(i, k)
            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;
        }
    }
    // (4) 求解答案
    //最后统计一下以哪个点为终点的长链的点的数目最多,将他所携带的点的数目和最长链数量揪出来
    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;
    //输出
    printf("%d\n", maxf);
    printf("%d\n", sum);
    return 0;
}

相关