跳转至

P8867 [NOIP2022] 建造军营 题解

题意

给定 \(n\) 个点 \(m\) 条边的无向连通图。你需要给其中一些点和边染色,保证去掉任意一条未染色的边之后,所有染色的点都是连通的。问染色方案数对 \(10^9+7\) 取模的结果。

题解

注意到如果去除的边不是,则不会改变图的连通性,因此我们需要将桥和其它边分开考虑。先用 tarjan 找出所有桥,然后 dfs 统计每个 bcc 中点和边的数量。这样问题就转化为一个带点权的树上问题。

记第 \(i\) 个边双分量包含 \(se[i]\) 条边和 \(sp[i]\) 个节点。考虑树上 dp:

  • \(f_{u,0}\) 表示 \(u\) 子树内没有染色的节点的方案数;
  • \(f_{u,1}\) 表示 \(u\) 子树内有至少一个被染色的节点,并且这些节点都通过被染色的边和根节点相连的方案数;
  • \(f_{u,2}\) 表示 \(u\) 子树内有至少一个被染色的点,它们在子树内通过被染色的边相互连通,但不与根节点相连的方案数;

答案就是 \(f_{rt,1}+f_{rt,2}\)。考虑转移(依次合并子树形式,直接按定义模拟即可):

\[ \begin{align*} f'_{u,0}&=f_{u,0}\times 2f_{v,0}\\ f'_{u,1}&=f_{u,0}\times f_{v,1}+f_{u,1}\times (f_{v,1}+2f_{v,0})\\ f'_{u,2}&=f_{u,0}\times (2f_{v,2}+f_{v,1})+f_{u,2}\times 2f_{v,0} \end{align*} \]

初值:

\[ \begin{align*} f_{u,0}&=2^{se[u]}\\ f_{u,1}&=2^{se[u]}(2^{sp[u]}-1)\\ f_{u,2}&=0 \end{align*} \]

注意取模。

AC 代码
#include<iostream>
#define int long long
using namespace std;
const int N = 5E5 + 10;
const int M = 1E6 + 10;
const int MOD = 1e9 + 7;

struct Edge2 {
    int u, v;
} edg[N];
int ne2;

struct Edge {
    int v, next;
} pool[2 * M];
int ne, head[N];

void addEdge(int u, int v) {
    pool[++ne] = {v, head[u]};
    head[u] = ne;
}

int n, m;

int col[N], sp[N], se[N], bccCnt;
bool isbri[2 * M];

namespace bcc {
    int dfn[N], low[N], dt;
    void tarjan(int u, int fa) {
        low[u] = dfn[u] = ++dt;
        for(int i = head[u]; i; i = pool[i].next) {
            int v = pool[i].v;
            if(v == fa) continue;
            if(!dfn[v]) {
                tarjan(v, u);
                low[u] = min(low[u], low[v]);
                if(low[v] > dfn[u]) {
                    isbri[i] = 1;
                    isbri[((i - 1) ^ 1) + 1] = 1;
                }
            } else {
                low[u] = min(low[u], dfn[v]);
            }
        }
    }
    void dfs(int u) {
        col[u] = bccCnt;
        sp[bccCnt]++;
        for(int i = head[u]; i; i = pool[i].next) {
            if(isbri[i]) continue;
            int v = pool[i].v;
            se[bccCnt]++;
            if(!col[v]) dfs(v);
        }
    }
    int fa[N];
    int find(int x) {
        if(fa[x] == x) return x;
        return fa[x] = find(fa[x]);
    }
    void merge(int x, int y) {
        fa[find(x)] = find(y);
    }
    void getScc() {
        tarjan(1, 0);
        for(int i = 1; i <= n; i++) {
            if(!col[i]) {
                ++bccCnt;
                dfs(i);
                se[bccCnt] /= 2;
            }
        }
        for(int i = 1; i <= n; i++) fa[i] = i;
        for(int u = 1; u <= n; u++) {
            for(int i = head[u]; i; i = pool[i].next) {
                int v = pool[i].v;
                if(col[u] != col[v] && find(col[u]) != find(col[v])) {
                    edg[++ne2] = {col[u], col[v]};
                    merge(col[u], col[v]);
                }
            }
        }
        for(int i = 1; i <= n; i++) head[i] = 0;
        ne = 0;
        if(ne2 + 1 != bccCnt) throw -1;
        for(int i = 1; i <= ne2; i++) {
            addEdge(edg[i].u, edg[i].v);
            addEdge(edg[i].v, edg[i].u);
        }
        n = bccCnt;
    }
}

int f[N][3];
namespace dp {
    inline int qpow(int a, int b) {
        int res = 1;
        while(b) {
            if(b & 1) res = res * a % MOD;
            a = a * a % MOD;
            b >>= 1;
        }
        return res;
    }
    void dfs(int u, int fa) {
        f[u][0] = qpow(2, se[u]);
        f[u][1] = qpow(2, se[u]) * (qpow(2, sp[u]) - 1 + MOD) % MOD;
        for(int i = head[u]; i; i = pool[i].next) {
            int v = pool[i].v;
            if(v == fa) continue;
            dfs(v, u);
            f[u][2] = (f[u][0] * (2 * f[v][2] + f[v][1]) % MOD + 2 * f[u][2] * f[v][0] % MOD) % MOD;
            f[u][1] = (f[u][0] * f[v][1] % MOD + f[u][1] * (f[v][1] + 2 * f[v][0]) % MOD) % MOD;
            f[u][0] = 2 * f[u][0] * f[v][0] % MOD;
        }
    }
}

signed main() {

    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);

    cin >> n >> m;
    for(int i = 1; i <= m; i++) {
        int u, v;
        cin >> u >> v;
        addEdge(u, v);
        addEdge(v, u);
    }

    bcc::getScc();
    dp::dfs(1, 0);

    cout << (f[1][1] + f[1][2]) % MOD << endl;

    return 0;
}