题解ZJOI2017仙人掌

Posted twilight-sx

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了题解ZJOI2017仙人掌相关的知识,希望对你有一定的参考价值。

  感觉这题很厉害啊,虽然想了一天多但还是失败了……(;д;)

  这题首先注意到给定图中如果存在环其实对于答案是没有影响的。然后关键之处就在于两个 (dp) 数组,其中 (f[u]) 表示以 (u) 为根的子树中能构成仙人掌的方案数, 而 ( g[x] ) 则表示 (x) 个节点之间两两相互搭配(可以不搭配)的总方案数。转移则为:

 (f[u] = prod f[v] * g[tot + [u != root]])

  其中 (v) 为 (u) 的儿子节点,而 (tot) 表示 (u) 的总儿子个数。为什么这样做是对的呢?我也感到非常的困惑。之前自己在思考的时候其实有一个问题一直难住我:一个节点的儿子之间可以相互连边,这怎样处理?但此时我们将这些方案巧妙地连接在了一起。我们可以默认为求出来的 (f[u]) 中的方案数均为有一条边连向外界的方案。当这个方案匹配到另一子树的一种方案上的时候,表示这两条连向外界的边连接在了一起。若有没有匹配的,说明这条边没有连出去或连向根节点(若连向根节点且该点为儿子节点则说明没有连出去),但一样是合法的。

  非常的厉害啊~其实感觉自己现在各种知识储备都还算可以了,但就是不够大胆,不能勇敢的提出一些想法和设想。一定要努力放开自己的思维,先猜测,再证明~

#include <bits/stdc++.h>
using namespace std;
#define maxn 1000000
#define mod 998244353
#define int long long
int n, m, dep[maxn], g[maxn];
int timer, dfn[maxn], f[maxn];
int fa[maxn], mark[maxn], tot;
int cnt, ans;

struct edge
{
    int cnp, head[maxn], to[maxn], last[maxn];
    edge() { cnp = 1; }
    void add(int u, int v)
    {
        to[cnp] = v, last[cnp] = head[u], head[u] = cnp ++;
        to[cnp] = u, last[cnp] = head[v], head[v] = cnp ++;
    }
}E1;

struct node
{
    int id, dep;
}a[maxn];

bool cmp(node a, node b) { return a.dep < b.dep; }

int read()
{
    int x = 0, k = 1;
    char c;
    c = getchar();
    while(c < 0 || c > 9) { if(c == -) k = -1; c = getchar(); }
    while(c >= 0 && c <= 9) x = x * 10 + c - 0, c = getchar();
    return x * k;
}

void pre()
{
    g[0] = g[1] = 1;
    for(int i = 2; i < maxn; ++ i) g[i] = (g[i - 1] + (i - 1)*g[i - 2]) % mod;
    return;
}

void Tarjan(int u)
{
    dfn[u] = ++ timer;
    for(int i = E1.head[u]; i; i = E1.last[i])
    {
        int v = E1.to[i]; if(dfn[v]) continue;
        fa[v] = u; dep[v] = dep[u] + 1; Tarjan(v);
    }
    return;
}

void dfs(int u, int rt)
{
    mark[u] = -1; f[u] = 1; int tot = 0;
    for(int i = E1.head[u]; i; i = E1.last[i])
    {
        int v = E1.to[i]; if(v == fa[u] || mark[v] != 1) continue;
        tot ++; dfs(v, 0); f[u] = f[u] * f[v] % mod;
    }
    if(!rt) f[u] = f[u] * g[tot + 1] % mod;
    else f[u] = f[u] * g[tot] % mod;
    return;
}

void Work()
{
    n = read(), m = read(); E1.cnp = 2;
    for(int i = 1; i <= n; i ++) mark[i] = fa[i] = dep[i] = dfn[i] = E1.head[i] = 0;
    for(int i = 1; i <= m; i ++)
    {
        int u = read(), v = read();
        E1.add(u, v);
    }
    dep[1] = 1; Tarjan(1);
    for(int i = 1; i <= m; i ++)
    {
        int u = E1.to[i << 1], v = E1.to[i << 1 | 1];
        if(dfn[u] < dfn[v]) swap(u, v);
        while(u != v) 
        {
            if(mark[u] == 2) { printf("0
"); return; }
            mark[u] ++; u = fa[u];
        }
    }
    for(int i = 1; i <= n; i ++) a[i].id = i, a[i].dep = dep[i];
    sort(a + 1, a + n + 1, cmp); ans = 1;
    for(int i = 1; i <= n; i ++)
    {
        int x = a[i].id; if(mark[x] == -1) continue;
        dfs(x, 1); ans = ans * f[x] % mod;
    }
    printf("%lld
", ans); return;
}

signed main()
{
    pre(); int T = read();
    while(T --) Work();
    return 0;
}

 

  

以上是关于题解ZJOI2017仙人掌的主要内容,如果未能解决你的问题,请参考以下文章

ZJOI2017仙人掌

●洛谷P3687 [ZJOI2017]仙人掌

「ZJOI2017」仙人掌

[BZOJ4784][ZJOI2017]仙人掌(树形DP)

做题ZJOI2017仙人掌——组合计数

ZJOI2017 仙人掌