AT_ddcc2017_final_d なめらかな木 的线性做法

给定一棵 \(N\) 个点的树,你需要给每一个节点编号 \(a_i\),使得所有点的编号构成一个 \(1\sim N\) 的排列,问有多少种方法使得任意相邻节点编号差不超过 \(2\),答案对 \(10^9+7\) 取模。

\(\texttt{Data Range:}\;N\le50\texttt{; Time Limit: 2000ms; Memory Limit: 256MB}\)

本题解采用自顶向下的方式说明如何设出状态以及转移方程。

如果一个点的度数 \(>4\),那么显然答案为 \(0\),记 \(T(x,y)\) 表示以 \(x\) 为根,砍掉所有 \(y\) 对应的子树后得到的树,其中 \(y\) 中的每个元素都是 \(x\) 为根时的子节点。

否则,记 \(f(x,y)\) 表示只考虑树 \(T(x,y)\),且 \(a_x=1\) 时的方案数,则答案为 \(\sum\limits_{i=1}^{n}f(x,\varnothing)\)

考虑根据 \(x\) 删去 \(y\) 对应的子树后的情况分类讨论:

  • 如果此时 \(x\) 的儿子个数为 \(0\),显然答案为 \(1\)
  • 如果此时 \(x\) 的儿子个数大于 \(2\),显然答案为 \(0\)
  • 如果此时 \(x\) 的儿子个数为 \(2\),那么我们需要将两个儿子分别填入 \(2,3\)\(3,2\)
    此时,我们记 \(g(x,y,X,Y)\) 表示假设同时考虑 \(T(x,y)\)\(T(X,Y)\) 这两棵子树,且强制要求 \(a_x=1,a_X=2\) 时的方案数,那么这种情况可由 \(g\) 转移。
  • 如果此时 \(x\) 的儿子个数为 \(1\),我们找到第一个分岔点,记为 \(z\),我们再根据 \(z\) 的度数分类讨论:
    • 如果 \(z\) 的度数为 \(1\),则此时为一条链,可以预处理出此时对应的答案 \(\mathit{dp1}_{\mathit{len}}\),其中 \(\mathit{len}\) 为链上的节点数量。具体的,\(\mathit{dp1}_x\) 表示 \(x\) 个点的链,要求第一个节点对应的数字为 \(1\) 的方案数。
    • 如果 \(z\) 的度数为 \(4\),假设它对应的数字是 \(v\),那么它相邻四个节点上的数字就是 \(v-2,v-1,v+1,v+2\),显然可以被划分为 \((v-2,v-1)\) 以及 \((v+1,v+2)\) 两组,彼此之间独立。
      • 我们再记 \(h_1(x,y,z)\) 表示考虑 \(T(x,y)\) 以及一条长度为 \(z\) 的链,其中 \(a_x=1\),链首值为 \(2\),链末值最大;\(h_2(x,y,z)\) 表示考虑 \(T(x,y)\) 以及一条长度为 \(z\) 的链,其中 \(a_x=2\),链首值为 \(1\),链末值最大。再记 \(h(x,y,z)=h_1(x,y,z)+h_2(x,y,z)\)
      • 则这种情况可以将三棵非链端子树以及一条链分成两组,方案数为 \(g\)\(h\) 的乘积。
    • 如果 \(z\) 的度数为 \(3\),记 \(x\)\(z\) 这条链上的边数为 \(c\),假设它对应的数字是 \(v\),那么根据它相邻三个节点以及子树内的 \(a\) 分类讨论(假设靠近链的节点为 \(p\),其余两个节点为 \(q,r\)\(a_q<a_r\)):
      • 如果 \(a_q=v+1,a_r=v+2,a_p=v-2\)\(v-1\)\(q\) 的子树内,我们枚举 \(v-1\)\(q\) 的哪个子树内,那么此时 \((v-1,v-2)\)\((v+1,v+2)\) 就独立了,可以由一个 \(g\) 以及一个 \(h_1\) 的乘积转移而来;
      • 如果 \(a_q=v+1,a_r=v+2,a_p=v-2\)\(a_p=v-1\),且 \(v-1\) 不在 \(q\) 的子树内,那么此时的 \((v+1,v+2)\) 和这条链就独立了,可以由一个 \(p\) 和一个 \(\mathit{dp2}_c+\mathit{dp3}_c\) 的乘积转移而来。具体的,\(\mathit{dp2}_x\) 表示 \(x\) 个点的链,要求第一个节点对应的数字为 \(1\),最后一个节点对应的数字为 \(x\) 的方案数;\(\mathit{dp3}_x\) 表示 \(x\) 个点的链,要求第一个节点对应的数字为 \(1\),最后一个节点对应的数字为 \(x-1\) 的方案数。
      • 如果 \(a_q=v-2,a_r=v+2,a_p=v+1\),那么此时可以 \((v+1,v+2)\) 就独立出来了,可以由一个 \(f\) 和一个 \(h_2\) 转移而来。
      • 如果 \(a_q=v-1,a_r=v+2,a_p=v-2\)\(v+1\)\(q\) 的子树内,那么这种情况与第一种情况类似;
      • 否则,必然有 \(a_r=v+1\)\(v+2\),且 \(a_p,a_q\) 一个为 \(v-1\) 一个为 \(v-2\),在排除掉上面一种情况后,\((v-2,v-1)\) 又独立了!所以可以由 \(x\) 去掉 \(p\)\(q\) 为根的子树对应的 \(f\) 的一个 \(h\) 的乘积转移而来。

综上所述,我们完成了 \(f\) 的转移,接下来我们讨论 \(g(x,y,X,Y),h_1(x,y,z),h_2(x,y,z),\mathit{dp1}_x,\mathit{dp2}_x,\mathit{dp3}_x\) 的转移。

先来讨论比较简单的 \(\mathit{dp1}_x,\mathit{dp2}_x,\mathit{dp3}_x\),当 \(x\ge4\) 时,我们容易得到:

  • \(\mathit{dp1}_x=\mathit{dp1}_{x-3}+\mathit{dp1}_{x-1}+1\)
  • \(\mathit{dp2}_x=\mathit{dp2}_{x-3}+\mathit{dp2}_{x-1}\)
  • \(\mathit{dp3}_x=\mathit{dp3}_{x-3}+\mathit{dp3}_{x-1}\)

考虑 \(g(x,y,X,Y)\),我们记 \(T(x,y)\)\(T(X,Y)\)\(x,X\) 的度数分别为 \(p,q\),当 \(p=2\) 时显然答案为 \(0\);当 \(p=0\) 时可以规约回 \(f\),所以只需考虑 \(p=1\) 的情况。

\(p=1\) 成立时,如果 \(q=0\) 我们仍然可以规约回去,\(q=2\) 时答案仍为 \(0\),所以只需考虑 \(p=q=1\) 的情况。

这个时候,我们发现 \(x\)\(X\) 唯一相邻的点上填的数是确定的。所以可以迭代计算,为了加速,可以分别找到第一个分岔点,记两条链的较小长度为 \(c\),则可以一次性迭代 \(c\) 次,这可以预处理后 \(\mathcal{O}(1)\) 查询所到节点。

发现 \(h_1\)\(h_2\) 也可以类似分讨处理,在此不做赘述。

综上所述,有关 \(f\) 的全部状态转移可以全部使用 \(f\) 进行转移。至此,本题在 \(\mathcal{O}(N)\) 的时间复杂度内得解!

点击查看代码
#include <bits/stdc++.h>

using namespace std;

const int N = 55, P = 1e9 + 7;

struct MOD {
    int operator ()(long long x) {
        return x - (((__int128)x * 18446743944) >> 64) * P;
    }
} mod;

vector<int> G[N];
int f[N][1 << 4], cnt[1 << 4];

int id(int x, int y) {
    for (int i = 0; i < (int)G[x].size(); ++i) {
        if (y == G[x][i]) return 1 << i;
    }
    return -1;
}

vector<int> adj(int x, int y) {
    vector<int> arr;
    for (int i = 0; i < (int)G[x].size(); ++i) {
        if (!((y >> i) & 1)) arr.push_back(G[x][i]);
    }
    return arr;
}

tuple<int, int, int> nxt[N][1 << 4];

tuple<int, int, int> getNxt(int x, int y) {
    if (~get<0>(nxt[x][y])) return nxt[x][y];
    vector<int> z = adj(x, y);
    if (z.empty()) return nxt[x][y] = {0, 0, 0};
    if (z.size() == 1) {
        nxt[x][y] = getNxt(z[0], id(z[0], x));
        ++get<2>(nxt[x][y]);
        return nxt[x][y];
    }
    return nxt[x][y] = {x, G[x][__builtin_ctz(y)], 0};
}

int sum[N], to[N << 1];
vector<int> tG[N << 1];
int deg[N << 1];
vector<int> ptr;

void dfs(int x) {
    ptr.push_back(x);
    for (auto v : tG[x]) dfs(v);
}

vector<int> arr[N << 1];
pair<int, int> pid[N << 1];

int ID(int x, int y) {
    return sum[x - 1] + __builtin_ctz(id(x, y)) + 1;
}

int walk(int x, int y, int c) {
    if (!c) return x;
    vector<int> z = adj(x, y);
    int tid = ID(x, z[0]);
    return arr[pid[tid].first][pid[tid].second + c - 1];
}

int dp1[N], dp2[N], dp3[N];

// Ban Y.

int _F(int x, int y);               // $x = 1
int _G(int x, int y, int X, int Y); // $x = 1, $X = 2
int _H(int x, int y, int L);        // H1 + H2
int _H1(int x, int y, int L);       // $x = 1, $head(L) = 2, $tail(L) = MAX
int _H2(int x, int y, int L);       // $x = 2, $head(L) = 1, $tail(L) = MAX

int _F(int x, int y) {
    if (~f[x][y]) return f[x][y];
    if (G[x].size() - cnt[y] == 0) return f[x][y] = 1;
    if (G[x].size() - cnt[y] == 1) {
        auto [a, b, c] = getNxt(x, y);
        if (!a) return dp1[c + 1];
        vector<int> z = adj(a, id(a, b));
        if (z.size() == 3) {
            int res = 0;
            sort(z.begin(), z.end());
            do {
                res = mod(res + 1ll * _G(z[0], id(z[0], a), z[1], id(z[1], a)) * _H(z[2], id(z[2], a), c));
            } while (next_permutation(z.begin(), z.end()));
            return f[x][y] = res;
        }
        int res = 0;
        sort(z.begin(), z.end());
        do {
            vector<int> tz = adj(z[0], id(z[0], a));
            res = mod(res + 1ll * _G(z[0], id(z[0], a), z[1], id(z[1], a)) * (dp2[c] + dp3[c]));                              // $z[0] = x + 1, $z[1] = x + 2, $b = x - 2 or x - 1 expect ↓
            for (auto v : tz) {
                if (v == z[0]) continue;
                res = mod(res + 1ll * _H1(v, id(v, z[0]), c) * _G(z[0], id(z[0], a) | id(z[0], v), z[1], id(z[1], a)));       // $z[0] = x + 1, $z[1] = x + 2, $b = x - 2, x - 1 IN Subtree(z[0])
            }
            if (c != 1) res = mod(res + 1ll * _F(z[1], id(z[1], a)) * _H2(z[0], id(z[0], a), c - 1));                         // $z[0] = x - 2, $z[1] = x + 2, $b = x + 1
            res = mod(res + 1ll * _H(z[0], id(z[0], a), c) * _F(a, id(a, b) | id(a, z[0])));                                  // $z[1] = x + 1 or x + 2, ($z[0], $b) = (x - 2, x - 1) expect ↓
            for (auto v : tz) {
                if (v == z[0]) continue;
                res = mod(res + 1ll * _H1(z[0], id(z[0], a) | id(z[0], v), c) * _G(v, id(v, z[0]), z[1], id(z[1], a)));       // $z[0] = x - 1, $z[1] = x + 2, $b = x - 2, x + 1 IN Subtree(z[0])
            }
        } while (next_permutation(z.begin(), z.end()));
        return f[x][y] = res;
    }
    if (G[x].size() - cnt[y] == 2) {
        vector<int> z = adj(x, y);
        return f[x][y] = mod(_G(z[0], id(z[0], x), z[1], id(z[1], x)) + _G(z[1], id(z[1], x), z[0], id(z[0], x)));
    }
    return f[x][y] = 0;
}

int _G(int x, int y, int X, int Y) {
    if (G[x].size() - cnt[y] == 0) return _F(X, Y);
    if (G[x].size() - cnt[y] >= 2) return 0;
    if (G[X].size() - cnt[Y] == 0) {
        vector<int> z = adj(x, y);
        return _F(z[0], id(z[0], x));
    }
    if (G[X].size() - cnt[Y] >= 2) return 0;
    auto [a, b, c] = getNxt(x, y);
    auto [A, B, C] = getNxt(X, Y);
    int ta = walk(x, y, min(c, C)), tb = walk(x, y, min(c, C) - 1),
        tA = walk(X, Y, min(c, C)), tB = walk(X, Y, min(c, C) - 1);
    return _G(ta, id(ta, tb), tA, id(tA, tB));
}

int _H(int x, int y, int L) {
    return mod(_H1(x, y, L) + _H2(x, y, L));
}

int _H1(int x, int y, int L) {
    if (G[x].size() - cnt[y] == 0) return dp2[L];
    if (G[x].size() - cnt[y] >= 2) return 0;
    if (L == 1) return 0;
    auto [a, b, c] = getNxt(x, y);
    int ta = walk(x, y, min(c, L - 1)), tb = walk(x, y, min(c, L - 1) - 1);
    return _H1(ta, id(ta, tb), L - min(c, L - 1));
}

int _H2(int x, int y, int L) {
    if (L == 1) return 0;
    if (G[x].size() - cnt[y] == 0) return dp2[L - 1];
    if (G[x].size() - cnt[y] >= 2) return 0;
    auto [a, b, c] = getNxt(x, y);
    int ta = walk(x, y, min(c, L - 1)), tb = walk(x, y, min(c, L - 1) - 1);
    return _H2(ta, id(ta, tb), L - min(c, L - 1));
}

signed main() {
    int n; scanf("%d", &n);
    for (int i = 1, x, y; i < n; ++i) {
        scanf("%d%d", &x, &y);
        G[x].push_back(y);
        G[y].push_back(x);
    }
    for (int i = 1; i <= n; ++i) {
        if (G[i].size() > 4) return 0 & puts("0");
        for (int j = 0; j < 1 << 4; ++j) nxt[i][j] = {-1, -1, -1};
    }
    for (int i = 1; i < 1 << 4; ++i) {
        cnt[i] = cnt[i >> 1] + (i & 1);
    }
    dp1[1] = dp1[2] = 1, dp1[3] = 2;
    for (int i = 4; i <= n; ++i) dp1[i] = mod(dp1[i - 3] + dp1[i - 1] + 1);
    dp2[1] = dp2[2] = dp2[3] = 1;
    for (int i = 4; i <= n; ++i) dp2[i] = mod(dp2[i - 3] + dp2[i - 1]);
    dp3[1] = dp3[2] = 0, dp3[3] = 1;
    for (int i = 4; i <= n; ++i) dp3[i] = mod(dp3[i - 3] + dp3[i - 1]);
    for (int i = 1; i <= n; ++i) {
        sum[i] = sum[i - 1] + G[i].size();
        for (auto v : G[i]) to[ID(i, v)] = v;
    }
    for (int i = 1; i <= n; ++i) {
        for (auto v : G[i]) {
            if (G[v].size() != 2) continue;
            tG[ID(i, v)].push_back(ID(v, adj(v, id(v, i))[0]));
        }
    }
    int m = (n - 1) * 2;
    for (int i = 1; i <= m; ++i) {
        for (auto v : tG[i]) ++deg[v];
    }
    int r = 0;
    for (int i = 1; i <= m; ++i) {
        if (deg[i]) continue;
        ptr.clear(), dfs(i), ++r;
        for (int j = 0; j < (int)ptr.size(); ++j) {
            pid[ptr[j]] = {r, j};
            arr[r].push_back(to[ptr[j]]);
        }
    }
    memset(f, -1, sizeof(f));
    int res = 0;
    for (int i = 1; i <= n; ++i) {
        res = mod(res + _F(i, 0));
    }
    printf("%d\n", (res % P + P) % P);
    return 0;
}
posted @ 2025-07-08 20:46  hhoppitree  阅读(68)  评论(0)    收藏  举报