码迷,mamicode.com
首页 > 其他好文 > 详细

[题解] [ZJOI2017] 仙人掌

时间:2020-01-15 23:08:18      阅读:70      评论:0      收藏:0      [点我收藏+]

标签:print   为什么   并且   else   play   ret   int   spl   com   

题面

题解

首先仙人掌上是不会有新边的

那么经过变换之后, 会连边的就一定是一片森林

我们对于其中一棵树单独讨论, 由于每棵树互不影响, 将每棵树的答案乘起来就行了

我们可以把连一条新的边 \((u, v)\) 看做覆盖 \(u\) , \(v\) 两点之间的边

那么对于没有被覆盖的边 \((u, v)\) , 我们看做连了一条重边

于是问题转化为, 用若干条不重复的路径覆盖一棵树的方案是多少

我们设 \(f(u)\) 为覆盖了 \(u\)\(u\) 往上那一条连到父亲的边都被覆盖的方案数, 然后根的 \(f\) 不带连向父亲的那条边

再设 \(g(n)\) 代表 \(n\) 条从同一点出发的边进行完全覆盖的方案数


\[ \displaystyle g(n) = g(n - 1) + (n - 1) * g(n - 2) \]
这是什么意思呢, 我们对于最后一条边分析

若他被自己的这一条重边覆盖, 那么就是另外 \(n - 1\) 条边互连

要么就是他跟一条别的边覆盖, 别的边有 \(n - 1\) 种选择方式, 并且这两条边不能被覆盖过

也就是其他的 \(n - 2\) 互相覆盖

所以就有上述递推式了

\(u\) 的儿子节点个数为 \(num\) , 然后转移方程便是
\[ \displaystyle f(u) = \prod_{v\in son_u}f(v)*g(num+1-[u=root]) \]
毕竟跟没有向上连向父亲的那一条边

这个方程的意思是, 所有儿子互不影响, 所以乘起来, 然后所有 \(u\) 连出去的边互相匹配, 这个也与之前的互不影响

所以就是上述式子啦

至于为什么它能够代表全部的方案

我们知道一条边 \((u, v)\) 他可能与 \(v\) 的一些儿子向上的边相连, 这些儿子向上的边又和它们的儿子向上的边相连

所以他在 \(u\) 下的所有情况都枚举到了

然后 \((u, v)\) 这一条边, 又会在 \(u\) 中与 \(v\) 的兄弟或 \(u\) 的父亲相连或都不连, 然后又向上或与兄弟相连或不连

这就保证在除 \(u\) 以下其他的所有情况都枚举到了

拼起来就是都枚举到了

应该就是这样了

Code

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
const int N = 500005;
const int mod = 998244353; 
using namespace std;

int n, m, head[N], cov[N], dfn[N], sz[N], d[N], fa[N], flag, T, g[N], cnt, ans; 
struct edge { int to, nxt; } e[N << 1]; 

template < typename T >
inline T read()
{
    T x = 0, w = 1; char c = getchar();
    while(c < '0' || c > '9') { if(c == '-') w = -1; c = getchar(); }
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * w; 
}

inline void adde(int u, int v) { e[++cnt] = (edge) { v, head[u] }, head[u] = cnt; }

void dfs(int u)
{
    dfn[u] = ++cnt;
    for(int v, i = head[u]; i; i = e[i].nxt)
    {
        v = e[i].to; if(v == fa[u]) continue;
        if(!dfn[v])
            fa[v] = u, dfs(v);
        else if(dfn[v] > dfn[u])
        {
            d[u] -= 2; 
            for(int x = v; x != u; x = fa[x])
            {
                if(cov[x]) { flag = 1; return; }
                cov[x] = 1, d[x] -= 2; 
            }
        }
        if(flag) return; 
    }
}

int main()
{
    T = read <int> ();
    g[0] = g[1] = 1; 
    for(int i = 2; i <= 500000; i++)
        g[i] = (g[i - 1] + 1ll * (i - 1) * g[i - 2] % mod) % mod; 
    while(T--)
    {
        n = read <int> (), m = read <int> ();
        for(int i = 1; i <= n; i++)
            cov[i] = dfn[i] = head[i] = d[i] = 0; 
        cnt = 0;
        for(int u, v, i = 1; i <= m; i++)
        {
            u = read <int> (), v = read <int> ();
            d[u]++, d[v]++, adde(u, v), adde(v, u); 
        }
        cnt = 0;
        flag = 0, dfs(1);
        if(flag) { puts("0"); continue; }
        for(int i = (ans = 1); i <= n; i++)
            ans = 1ll * ans * g[d[i]] % mod;
        printf("%d\n", ans); 
    }
    return 0; 
}

我这里把每个点都在最后统计了

[题解] [ZJOI2017] 仙人掌

标签:print   为什么   并且   else   play   ret   int   spl   com   

原文地址:https://www.cnblogs.com/ztlztl/p/12199013.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!