• Solution 「牛客 31454H」Permutation on Tree


    \(\mathscr{Description}\)

      Link.

      给定一棵含有 \(n\) 个点的有根外向树, 对于所有满足树形拓扑关系的结点遍历顺序 \(p_{1..n}\) 求出 \(\sum_{i=2}^n|p_{i-1}-p_i|\) 之和. 答案对 \((10^9+7)\) 取模.

      \(\require{cancel} \cancel{n\le200}\) \(n\le5\times10^3\).

    \(\mathscr{Solution}\)

      膜拜理论信息学家 \(\textbf{OneInDark}\). 不仅踩爆标算, 还极大加速了 BI (Bunny Intelligence) 代码生成工具的开发进程.

      由于是被讲明白的, 所以可能会比较 unmotivated 呜.

      考虑枚举序列上相邻的编号 \(x,y\), 计数它们在多少个序列中出现.

      直接表示出想要求的东西? 设 \(w=\operatorname{lca}(u,v)\), 令 \(f(x,y)\) 表示仅考虑 \(w\) 子树内的序列, \(x,y\) 贴贴且认为 \(x,y\) 子树内结点间无序的方案数. 注意这里为了方便转移, 我们钦定了一些无序关系.

      为方便推导, 先预处理出 \(g(u)\) 表示 \(u\) 子树内的合法序列数量. 设 \(q_u\) 表示 \(u\) 的父亲, \(s_u\) 表示 \(u\) 的子树大小, 对于边界情况, \(q_x=q_y=w\) 时, 有

    \[f(x,y)=\binom{s_w-2}{s_x+s_y-1}\binom{s_w-1-s_x-s_y}{\{s_u\mid u\in\operatorname{son}(w)\setminus\{x,y\}\}}\prod_{u\in\operatorname{son}(u)\setminus\{x,y\}}g(u). \]

    即, 其他兄弟该怎么选怎么选; 对于 \(x,y\) 子树, 把 \(x,y\) 贴在一个位置之后只剩 \(s_w-2\) 个空位和 \(s_x+s_y-1\) 个结点需要放进去.

      对于其余情况, \(f(x,y)\) 自然是从 \(f(q_x,y)\)\(f(x,q_y)\) 转移. 考虑从 \(q_x\) 下降到 \(x\) 的过程, \(x\) 的兄弟们需要被确定顺序, 贴贴的位置继续下放没有影响, 因而

    \[f(x,y){\overset{+}{\longleftarrow}}\binom{s_{q_x}+s_y-2}{s_{q_x}-s_x-1}\binom{s_{q_x}-1-s_x}{\{s_u\mid u\in\operatorname{son}(q_x)\setminus\{x\}\}}\prod_{u\in\operatorname{son}(q_x)\setminus\{x\}}g(u)\cdot f(q_x,y). \]

    即, 选出 \(x\) 的兄弟们的位置, 选出它们之间的顺序, 各自内部再定序, 乘上转移到的状态. \(q_y\) 的转移完全对称. 不过仅当 \(q_x\neq w\) 或者 \(q_y\neq w\) 时才会发生上述转移.

      问题是, 用这玩意儿其实并不方便表示答案. 先假设 \(w\) 就是全局根, 令 \(r_w\) 表示 \(\operatorname{lca}\)\(w\) 的"答案", 那么

    \[r_w=\sum_{\operatorname{lca}(x,y)=w}|x-y|\binom{s_x+s_y-2}{s_x-1}g(x)g(y)f(x,y). \]

    其中中间三项系数就是补上 \(x,y\) 的定序. 注意 \(x,y\) 一定是子树内最先选出来的, 而且拿去贴贴了, 所有带了一些 \(-1\) 的常数.

      最后, 自然是把 \(r_w\) 的信息拿到真正的树根上去:

    \[r_{q_u}\overset{+}{\longleftarrow}\binom{s_{q_u}-2}{s_u-1}\binom{s_{q_u}-1-s_u}{\{s_v\mid v\in\operatorname{son}(q_u)\setminus\{u\}\}}\prod_{v\in\operatorname{son}(q_u)\setminus\{u\}}g(v)\cdot r_u. \]

    组合意义比较简单, 注意 \(u\) 子树内有一对点在贴贴, 所以大小会减小 \(1\).

      至于那些丑陋的 \setminus, 都可以在 \(g(u)\) 的基础上修修补补 \(\mathcal O(1)\) 个因子算出来. 精细处理一些需要使用的逆元, 可以做到严格的 \(\mathcal O(n^2)\).

      所以最奇妙的地方还是这个 \(f\) 的状态设计. 无序有序的钦定方便了后续转移.

    \(\mathscr{Code}\)

    /*+Rainybunny+*/
    
    #include <bits/stdc++.h>
    
    #define rep(i, l, r) for (int i = l, rep##i = r; i <= rep##i; ++i)
    #define per(i, r, l) for (int i = r, per##i = l; i >= per##i; --i)
    
    const int MAXN = 200, MOD = 1e9 + 7;
    int n, rt, ecnt, head[MAXN + 5], fac[MAXN + 5], ifac[MAXN + 5];
    int bino[MAXN + 5][MAXN + 5], ibino[MAXN + 5][MAXN + 5];
    int fa[MAXN + 5], f[MAXN + 5][MAXN + 5];
    int g[MAXN + 5], ig[MAXN + 5], siz[MAXN + 5], ans[MAXN + 5];
    struct Edge { int to, nxt; } graph[MAXN * 2 + 5];
    
    inline int iabs(const int u) { return u < 0 ? -u : u; }
    inline int mul(const int u, const int v) { return 1ll * u * v % MOD; }
    inline void subeq(int& u, const int v) { (u -= v) < 0 && (u += MOD); }
    inline int sub(int u, const int v) { return (u -= v) < 0 ? u + MOD : u; }
    inline void addeq(int& u, const int v) { (u += v) >= MOD && (u -= MOD); }
    inline int add(int u, const int v) { return (u += v) < MOD ? u : u - MOD; }
    inline int mpow(int u, int v) {
        int ret = 1;
        for (; v; u = mul(u, u), v >>= 1) ret = mul(ret, v & 1 ? u : 1);
        return ret;
    }
    
    inline void link(const int u, const int v) {
        graph[++ecnt] = { v, head[u] }, head[u] = ecnt;
        graph[++ecnt] = { u, head[v] }, head[v] = ecnt;
    }
    
    inline void init() {
        bino[0][0] = 1;
        rep (i, 1, n) {
            bino[i][0] = 1;
            rep (j, 1, i) bino[i][j] = add(bino[i - 1][j - 1], bino[i - 1][j]);
        }
        rep (i, 0, n) {
            ibino[i][0] = 1;
            rep (j, 1, i) ibino[i][j] = mul(ibino[i][j - 1], bino[i][j]);
            int s = mpow(ibino[i][i], MOD - 2);
            per (j, i, 1) {
                ibino[i][j] = mul(s, ibino[i][j - 1]);
                s = mul(s, bino[i][j]);
            }
        }
    }
    
    inline void getG(const int u) {
        g[u] = siz[u] = 1;
        for (int i = head[u], v; i; i = graph[i].nxt) {
            if ((v = graph[i].to) != fa[u]) {
                fa[v] = u, getG(v), siz[u] += siz[v];
                g[u] = mul(bino[siz[u] - 1][siz[v]], mul(g[u], g[v]));
            }
        }
        ig[u] = mpow(g[u], MOD - 2);
    }
    
    inline int calc(const int x, const int y, const int u) {
        int& ret = f[x][y];
        if (ret) return ret;
        if (fa[x] == u && fa[y] == u) {
            ret = mul(g[u], mul(mul(ibino[siz[u] - 1][siz[x]],
              ibino[siz[u] - siz[x] - 1][siz[y]]), mul(ig[x], ig[y])));
            ret = mul(ret, bino[siz[u] - 2][siz[x] + siz[y] - 1]);
            return ret;
        }
        if (fa[x] != u) {
            addeq(ret, mul(mul(bino[siz[fa[x]] + siz[y] - 2]
              [siz[fa[x]] - siz[x] - 1], mul(g[fa[x]], mul(ig[x],
              ibino[siz[fa[x]] - 1][siz[x]]))), calc(fa[x], y, u)));
        }
        if (fa[y] != u) {
            addeq(ret, mul(mul(bino[siz[fa[y]] + siz[x] - 2]
              [siz[fa[y]] - siz[y] - 1], mul(g[fa[y]], mul(ig[y],
              ibino[siz[fa[y]] - 1][siz[y]]))), calc(x, fa[y], u)));
        }
        return ret;
    }
    
    inline std::vector<int> solve(const int u) {
        std::vector<int> sub;
        int is = mpow(siz[u] - 1, MOD - 2);
        for (int i = head[u], v; i; i = graph[i].nxt) {
            if ((v = graph[i].to) != fa[u]) {
                fa[v] = u;
                addeq(ans[u], mul(iabs(u - v), mul(mul(siz[v], is), g[u])));
    
                auto&& tmp(solve(v));
                for (int x: sub) for (int y: tmp) {
                    int coe = mul(iabs(x - y) << 1, mul(g[x], mul(g[y],
                      bino[siz[x] + siz[y] - 2][siz[x] - 1])));
                    addeq(ans[u], mul(coe, calc(x, y, u)));
                }
    
                sub.reserve(sub.size() + tmp.size());
                for (int y: tmp) sub.push_back(y);
            }
        }
        return sub.push_back(u), sub;
    }
    
    inline void summary(const int u) {
        int is = mpow(siz[u] - 1, MOD - 2);
        for (int i = head[u], v; i; i = graph[i].nxt) {
            if ((v = graph[i].to) != fa[u]) {
                summary(v);
                int coe = mul(g[u], mul(bino[siz[u] - 2][siz[v] - 1],
                  mul(ig[v], ibino[siz[u] - 1][siz[v]])));
                addeq(ans[u], mul(ans[v], coe));
            }
        }
    }
    
    int main() {
        // freopen("tree.in", "r", stdin);
        // freopen("tree.out", "w", stdout);
    
        scanf("%d %d", &n, &rt), init();
        rep (i, 2, n) { int u, v; scanf("%d %d", &u, &v), link(u, v); }
        getG(rt), solve(rt), summary(rt);
        printf("%d\n", ans[rt]);
        return 0;
    }
    
    
  • 相关阅读:
    过程作为黑箱抽象——《计算机程序的构造和解释》
    过程与它们所产生的计算——《计算机程序的构造和解释》
    重构手法(四)之在对象之间搬移特性
    重构手法(三)之简化条件表达式
    重构手法(二)之简化函数调用
    重构手法(一)之重新组织函数
    代码的坏味道
    泛型算法(二十三)之排列算法
    泛型算法(二十二)之集合操作算法
    泛型算法(二十一)之比较算法
  • 原文地址:https://www.cnblogs.com/rainybunny/p/16444658.html
Copyright © 2020-2023  润新知