仙人掌[ZJOI2017]

题目描述

https://www.luogu.com.cn/problem/P3687

题面里咋还有张图啊,就不放这了

题解

首先,如果原图不是仙人掌,显然答案为 (0)

如果原图是一棵树,那么我们可以选择若干条边不相交的路径,如果某条路径长度大于一,就将路径的两个端点间加一条边,那么这样构造出来的图一定是一个仙人掌

所以原问题实际在问有多少种方案用若干条边不相交的路径覆盖原图

(f[x]) 表示考虑 (x) 的子树内和 (x) 到父亲的那条边,有多少种方案把这些边划分成若干条路径

假设 (x)(k) 个儿子,那么如果 (x) 不为根,就有 (k+1) 条从 (x) 连出的边,这些边可以两两配对连接形成穿过 (x) 的路径,也可以不配对

(p_i) 表示将 (i) 条边两两配对(或不配对)有多少种方案,考虑最后一条边是否和另一条边配对,易得递推式:(p_i=p_{i-1}+(i-1)*p_{i-2})

那么对于树边,有 (f[x]=(prodlimits_{yin son(x)} f[y]) * p_{k+1}) (如果 (x) 是根那么最后乘的应该是 (p_k))

如果原图不是树而是仙人掌呢?

考虑仙人掌上的环,从上面那个"路径覆盖"的角度思考,发现环的要求就是环上的边不能被路径覆盖,而树边必须被覆盖

所以直接把所有环上的边删掉得到一个森林,把每棵树的根的答案乘在一起就是最终答案了

当然这样做有点麻烦,维护一个 (g[x]) 表示只考虑 (x) 的子树内的边 ( (x) 到父亲的边可能是环边),有多少种方案把这些边划分成若干条路径

然后在碰到环时特殊处理一下即可

#include <bits/stdc++.h>
#define N 1000005
using namespace std;
typedef long long ll;

template <typename T>
inline void read(T &num) {
	T x = 0, ff = 1; char ch = getchar();
	for (; ch > '9' || ch < '0'; ch = getchar()) if (ch == '-') ff = -1;
	for (; ch <= '9' && ch >= '0'; ch = getchar()) x = (x << 3) + (x << 1) + (ch ^ '0');
	num = x * ff;
}

const ll mod = 998244353;
int ttt, n, m;
int head[N], pre[N<<1], to[N<<1], sz;
int dfn[N], low[N], stk[N], top, c[N], tot, tme;
ll f[N], g[N], p[N];
bool tmp[N], flag;

inline void addedge(int u, int v) {
	pre[++sz] = head[u]; head[u] = sz; to[sz] = v;
	pre[++sz] = head[v]; head[v] = sz; to[sz] = u;
}

void init() {
    for (int i = 1; i <= n; i++) head[i] = 0; sz = 0;
    for (int i = 1; i <= n; i++) dfn[i] = low[i] = 0, f[i] = 0;
    tme = top = 0; flag = 0;
}


void solve(int X) {
    //判断是否是仙人掌
    for (int i = 1; i <= tot; i++) tmp[c[i]] = 1;
    int cnt = 0;
    for (int i = 1; i <= tot; i++) {
        int x = c[i];
        for (int j = head[x]; j; j = pre[j]) if (tmp[to[j]]) cnt++;
    }
    if (cnt != tot * 2) flag = 1;
    for (int i = 1; i <= tot; i++) tmp[c[i]] = 0;
    //
    
    //维护DP值
    for (int i = 1; i < tot; i++) f[X] = f[X] * g[c[i]] % mod; //c[1]~c[tot-1]为环上除x之外的其它点
    //
}

void tarjan(int x) {
    dfn[x] = low[x] = ++tme; stk[++top] = x;
    f[x] = 1;
    int cnt = 0;
    for (int i = head[x]; i; i = pre[i]) {
        int y = to[i];
        if (!dfn[y]) {
            tarjan(y);
            low[x] = min(low[x], low[y]);
            if (dfn[x] == low[y]) {
                int z = 0; tot = 0;
                do {
                    z = stk[top--];
                    c[++tot] = z;
                } while (z != y);
                c[++tot] = x;
                if (tot > 2) solve(x); //找到环
                else {
                    cnt++;
                    f[x] = f[x] * f[y] % mod;
                }
            } 
        } else low[x] = min(low[x], dfn[y]);
    }
    g[x] = f[x] * p[cnt] % mod;
    f[x] = f[x] * p[cnt+(x!=1)] % mod;
}

int main() {
    read(ttt);
    p[0] = p[1] = 1;
    for (int i = 2; i <= 500000; i++) p[i] = (p[i-1] + p[i-2] * (i-1) % mod) % mod;
    while (ttt--) {
        read(n); read(m);
        for (int i = 1, u, v; i <= m; i++) {
            read(u); read(v);
            addedge(u, v);
        }
        tarjan(1); 
        if (flag) puts("0");
        else printf("%lld
", f[1]);
        init();
    }
    return 0;
}
原文地址:https://www.cnblogs.com/ak-dream/p/AK_DREAM112.html