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;
}

浙公网安备 33010602011771号