• 数树


    本来想叫 WC2019 数树 的

    后来发现自己还不怎么会怎么数树...

    那就来数数树吧

    Matrix-Tree 定理

    令 $G$ 是一个无向图,$D$ 为 $G$ 的度数矩阵,即 $D[i][i] = [i的度数]$,$A$ 为 $G$ 的邻接矩阵,即 $A[i][j] = [i,j之间边的数量]$

    定义 $G$ 的 基尔霍夫矩阵 $K$ 为 $D-A$

    则 $K$ 随便删掉一行一列,剩下的矩阵 $K'$ 的行列式的绝对值,即 $|det(K')|$ 为 $G$ 的生成树个数

    可以用来数 $n$ 个点有标号无根树的数量,因为 $n$ 个点有标号无根树就是 $n$ 个点的完全图的生成树

    因为求 $det$ 是 $O(n^3)$ 的所以这种方法大多数时间是 $O(n^3)$ 的,有些题有一些很好的性质,可以让你的行列式非常漂亮

    Caylay 公式

    非常漂亮的行列式

    公式的内容写在了 WC2019 数树 这道题里

    提示:$n$ 个节点的树一共有 $n^{n-2}$ 种

    有一些很有趣的扩展

    比如:$n$ 个点的生成森林里有 $m$ 个树,其中 $1,2,3,...,m$ 号点分别属于第 $1,2,...,m$ 棵树的方案数为 $m imes n^{n-m-1}$

    prufer 序列

    一个奇怪的序列

    一棵无根树,每次删掉编号最小的叶子,并记录它相邻的点的编号,直到树剩下 $2$ 个点

    这样得到一个长度为 $n-2$ 的序列

    每个点会在序列中出现 $它的度数-1$ 次

    于是无根树计数问题就可以在某种程度上转化为序列计数问题

    根据 prufer 序列也可以整出一些很厉害的公式比如

    一个 $m$ 棵树的森林共 $n$ 个点,第 $i$ 棵树有 $a_i$ 个点的方案数为 $n^{m-2} imes prodlimits_{i=1}^m a_i$

    一些简单的数树题:

    bzoj1005 明明的烦恼

    给你 n ,和树上一些点的度数,求满足条件的有标号无根树

    转成 prufer 序列,就是钦定了一些元素的出现次数的长度为 n-2 的序列计数

    设给你度的点有 x 个,转化一下变成了 prufer 序列中给定的值有 $y=sumlimits_{i=1}^x (d_i-1)$ 个那么给这 y 个钦定位置有 $C_{n-2}^y$ 种,其中每一种都钦定了出现次数,所以这个长度为 y 的序列有 $frac{(y-2)!}{prod [(d_i-1)!]}$ 种

    剩下 $n-y-2$ 个位置可以放剩下的任意一个点,就是 $(n-x)^{(n-y-2)}$ 种放法

    都乘起来就行了

    codeforces1109E

    给你n,m,a,b

    求有多少n个点的有边权的树,满足边权上限是m,a点到b点的距离为m

    首先枚举 a 到 b 有多少条边,假设有 e 条边

    首先选出 e 条边,也就是 e-1 个点(因为有 a,b),注意到有顺序,所以是 $A_{n-2}^{e-1}$

    然后把这 m 的距离分成 e 个非空的份,用插板,有 $C_{m-1}^{e-1}$ 种分法

    剩下的边权随便,有 $m^{n-e-1}$ 种赋值方法

    然后剩下的点可以挂在 a~b 这条链上,用一下 Caylay 公式扩展:

    一共有 n 个点,e 棵树,a,...,b 分别属于第 1,...,e 棵树

    方案数是 $e imes n^{n-e-1}$ 

    乘起来就行了

    WC 2019 数树

    一开始 $ans=y^n$,有两棵 $n$ 个点的树,每有一条重边,$ans$ 就除以 $y$,现在给你 $y,n$

    有 3 个 subtask

    0.给你两棵树的具体形态,求 $ans$

    1.给你一棵树的具体形态,对于所有 $n$ 个点的树,求它和给你那棵树的 $ans$ 之和

    2.对于所有 $n$ 个点的树,求 1. 的答案之和

    subtask0. map 裸题

    subtask1.

    两条路

    1.Matrix-Tree 定理

    这个 $n^3$ 很好写,但矩阵长得没那么好看...后面神仙推导我不会

    对于给你的树的每一条边,我们连 $y^{-1}$ 条,剩下的连成完全图,然后上矩阵树定理就可以了

    (后面不会)

    2.奇怪的操作

    记 $iy = y^{-1}$,这样两棵树的贡献就是 $iy^{重边数}$ ,设 $k=重边数$ ,把 $iy^{k}$ 展开

    $iy^k = (iy-1+1)^k = sumlimits_{i=1}^k C_{k}^{i} imes (iy-1)^i$ 

    我们可以枚举 $n$ 个点的完全图的每一个边的子集,对于一个边集 $E$ ,如果它同时是这两棵树的子集,答案就加上 $(iy-1)^{|E|}$ ,如果不是,就不加

    可以发现,如果原来两棵树的交集有 $k$ 条边,会有 $C_{k}^{i}$ 个 $i$ 条边的交集子集被算一次,与前面那个式子殊途同

    我们可以枚举一个给定树的边集,把它的贡献算出来

    那也就是要算 $n$ 个点,已经连了一些边,生成树的方案数

    用前面的公式可以知道,如果这些边把图分成了 $m$ 个连通块,每块的大小为 $a_i$,方案数是 $n^{m-2} imes prodlimits_{i=1}^m a_i$,总贡献就是 $方案数 imes (iy-1)^{n-m}$

    看后面那个连乘的组合意义,发现是每块内选一个点的方案数

    用 $f_{(i,0/1)}$ 表示 $i$ 所在的块里选了 / 没选点的方案数

    dp 即可,转移的时候讨论选不选这条边,然后考虑新生成的边数和连通块数就可以了

    subtask2.

    放弃写题解了,多项式 exp

    题解写出来的 latex 式子可能跟多项式 exp 的板子一样长

    #include <bits/stdc++.h>
    #define LL long long
    #define rep(i, s, t) for (register int i = (s), i##end = (t); i <= i##end; ++i)
    #define dwn(i, s, t) for (register int i = (s), i##end = (t); i >= i##end; --i)
    using namespace std;
    inline int read() {
        int x = 0, f = 1;
        char ch;
        for (ch = getchar(); !isdigit(ch); ch = getchar())
            if (ch == '-')
                f = -f;
        for (; isdigit(ch); ch = getchar()) x = 10 * x + ch - '0';
        return x * f;
    }
    const int maxn = 600010, mod = 998244353;
    inline int skr(int x, int t) {
        int res = 1;
        for (; t; x = 1LL * x * x % mod, t >>= 1)
            if (t & 1)
                res = 1LL * res * x % mod;
        return res;
    }
    int n, y, op;
    int first[maxn], to[maxn << 1], nx[maxn << 1], cnt;
    inline void add(int u, int v) {
        to[++cnt] = v;
        nx[cnt] = first[u];
        first[u] = cnt;
    }
    namespace subtask0 {
    map<int, int> mp[maxn];
    void solve() {
        rep(i, 1, n - 1) {
            int u = read(), v = read();
            add(u, v);
            add(v, u);
        }
        rep(i, 1, n - 1) {
            int u = read(), v = read();
            mp[u][v] = mp[v][u] = 1;
        }
        int cnt = 0;
        rep(cur, 1, n) for (int i = first[cur]; i;
                            i = nx[i]) if (to[i] < cur && mp[cur].find(to[i]) != mp[cur].end()) cnt++;
        cout << skr(y, n - cnt) << endl;
    }
    }  // namespace subtask0
    namespace subtask1 {
    int f[maxn][2], iy;
    void dfs(int x, int fa) {
        f[x][0] = f[x][1] = 1;
        for (int i = first[x]; i; i = nx[i]) {
            if (to[i] == fa)
                continue;
            dfs(to[i], x);
            int v = to[i];
            LL tmp;
            tmp = 1LL * f[x][1] * f[v][1] % mod * n % mod;
            (tmp += (((LL)f[x][1] * f[v][0] % mod + (LL)f[x][0] * f[v][1] % mod) * (iy - 1) % mod)) %= mod;
            f[x][1] = tmp;
            tmp = 1LL * f[x][0] * f[v][0] % mod * (iy - 1) % mod;
            (tmp += (1LL * f[v][1] * f[x][0] % mod * n % mod));
            f[x][0] = tmp;
        }
    }
    void solve() {
        if (y == 1) {
            cout << skr(n, n - 2) << endl;
            return;
        }
        iy = skr(y, mod - 2);
        rep(i, 1, n - 1) {
            int u = read(), v = read();
            add(u, v);
            add(v, u);
        }
        dfs(1, 0);
        cout << 1LL * (1LL * skr(y, n) * f[1][1] % mod) * skr(n, mod - 2) % mod << endl;
    }
    }  // namespace subtask1
    namespace subtask2 {
    int r[maxn], lg[maxn], temp[maxn];
    int inv[maxn], ifac[maxn], fac[maxn];
    inline int skr(int x, LL t) {
        int res = 1;
        while (t) {
            if (t & 1)
                res = 1LL * res * x % mod;
            x = 1LL * x * x % mod;
            t = t >> 1;
        }
        return res;
    }
    inline void fft(int *a, int n, int type) {
        for (int i = 0; i < n; i++) r[i] = (r[i >> 1] >> 1) | ((i & 1) << (lg[n] - 1));
        for (int i = 0; i < n; i++)
            if (i < r[i])
                swap(a[i], a[r[i]]);
        for (int i = 1; i < n; i <<= 1) {
            int wn = skr(3, (mod - 1) / (i << 1));
            if (type == -1)
                wn = skr(wn, mod - 2);
            // cout << wn << endl;
            for (int j = 0; j < n; j += (i << 1)) {
                int w = 1;
                for (int k = 0; k < i; k++, w = (1LL * (LL)w * (LL)wn) % mod) {
                    int x = a[j + k], y = (1LL * (LL)w * (LL)a[j + k + i]) % mod;
                    a[j + k] = (x + y) % mod;
                    a[j + k + i] = (((x - y) % mod) + mod) % mod;
                }
            }
        }
        if (type == -1) {
            int inv = skr(n, mod - 2);
            for (int i = 0; i < n; i++) a[i] = ((LL)a[i] * (LL)inv) % mod;
        }
    }
    inline void Inverse(int *a, int *b, int n) {
        if (n == 1) {
            b[0] = skr(a[0], mod - 2);
            return;
        }
        Inverse(a, b, n >> 1);
        memcpy(temp, a, n * sizeof(int));
        memset(temp + n, 0, n * sizeof(int));
        fft(temp, n << 1, 1);
        fft(b, n << 1, 1);
        for (int i = 0; i < (n << 1); i++)
            b[i] = 1LL * b[i] * ((2LL - 1LL * temp[i] * b[i] % mod + mod) % mod) % mod;
        fft(b, n << 1, -1);
        memset(b + n, 0, n * sizeof(int));
    }
    int c[maxn], d[maxn];
    inline void Ln(int *a, int *b, int n) {
        Inverse(a, c, n);
        for (int i = 0; i < n - 1; ++i) d[i] = (LL)(i + 1) * a[i + 1] % mod;
        d[n - 1] = 0;
        fft(c, n << 1, 1);
        fft(d, n << 1, 1);
        for (int i = 0; i < (n << 1); ++i) c[i] = 1LL * d[i] * c[i] % mod;
        fft(c, (n << 1), -1);
        for (int i = 1; i < (n << 1); ++i) b[i] = 1LL * inv[i] * c[i - 1] % mod;
        b[0] = 0;
        for (int i = 0; i < (n << 1); ++i) c[i] = d[i] = 0;
    }
    int temp_w[maxn], temp_Ln[maxn];
    inline void Exp(int *a, int *b, int n) {
        if (n == 1) {
            b[0] = 1;
            return;
        }
        Exp(a, b, n >> 1);
        memcpy(temp_w, b, sizeof(int) * n);
        memset(temp_w + n, 0, sizeof(int) * n);
        Ln(b, temp_Ln, n);
        for (int i = 0; i < n; i++) temp_Ln[i] = (mod + a[i] - temp_Ln[i]) % mod;
        (temp_Ln[0] += 1) %= mod;
        fft(temp_w, n << 1, 1);
        fft(temp_Ln, n << 1, 1);
        for (int i = 0; i < (n << 1); i++) temp_w[i] = 1LL * temp_w[i] * temp_Ln[i] % mod;
        fft(temp_w, n << 1, -1);
        memcpy(b, temp_w, n * sizeof(int));
        memset(b + n, 0, n * sizeof(int));
        memset(temp_w, 0, sizeof(int) * (n << 1));
        memset(temp_Ln, 0, sizeof(int) * (n << 1));
    }
    int F[maxn], G[maxn];
    void solve() {
        lg[0] = -1;
        rep(i, 1, 600000) lg[i] = lg[i >> 1] + 1;
        if (y == 1) {
            cout << skr(n, 2 * n - 4) << endl;
            return;
        }
        int iy = skr(y, mod - 2);
        int m = 1;
        for (; m <= n; m <<= 1)
            ;
        inv[1] = ifac[0] = fac[0] = 1;
        rep(i, 1, m) {
            if (i != 1)
                inv[i] = -(LL)mod / i * inv[mod % i] % mod;
            inv[i] = ((inv[i] % mod) + mod) % mod;
            ifac[i] = (LL)ifac[i - 1] * inv[i] % mod;
            fac[i] = (LL)fac[i - 1] * i % mod;
        }
        int z = iy - 1;
        int t = 1LL * skr(z, mod - 2) * (1LL * n * n % mod) % mod;
        rep(i, 1, n) F[i] = 1LL * skr(i, i) * (1LL * t * ifac[i] % mod) % mod;
        Exp(F, G, m);
        LL ans = 1;
        ans = 1LL * skr(z, n) * fac[n] % mod;
        (ans *= (1LL * skr(n, mod - 5) * G[n] % mod)) %= mod;
        (ans *= skr(y, n)) %= mod;
        cout << ans << endl;
    }
    }  // namespace subtask2
    int main() {
        freopen("tree.in", "r", stdin);
        freopen("tree.out", "w", stdout);
        n = read(), y = read(), op = read();
        if (op == 0)
            subtask0::solve();
        else if (op == 1)
            subtask1::solve();
        else
            subtask2::solve();
    }
    View Code

    upd:清华爷题解传送门里面有我懒得写的 subtask 2

  • 相关阅读:
    MongoDB for OPS 02:复制集 RS 配置
    MongoDB for OPS 01:服务介绍与基本使用
    Redis for OPS 07:Redis 补充说明
    Redis for OPS 06:Redis Cluster 集群
    google ctemplate——c++模板引擎
    libctemplate——源码分析
    使用gulp对js、css、img进行合并压缩
    Windows平台交叉编译Arm Linux平台的QT5.7库
    使用gtest对DLL工程进行单元测试的实践
    websocket++简单使用例子
  • 原文地址:https://www.cnblogs.com/Kong-Ruo/p/10403370.html
Copyright © 2020-2023  润新知