专栏文章
题解:P4582 [FJOI2014] 树的重心
P4582题解参与者 1已保存评论 0
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 0 条
- 当前快照
- 1 份
- 快照标识符
- @miqauhbf
- 此快照首次捕获于
- 2025/12/04 01:47 3 个月前
- 此快照最后确认于
- 2025/12/04 01:47 3 个月前
树的重心
Problem
有 ()组测试数据。
每次给出 ()个节点的树,求它的连通子图数量,使得它的重心与整棵树的重心重合。答案对质数 取模。
Sol
不妨设 表示以 为根的时候, 的子树大小。
先找到树的重心。发现只有 的时候才会出现两个重心。
- 只有一个重心:
不妨记这个重心为 ,
令 表示 子树中,连通子图大小为 的数量。
有转移:。然后 。
做完之后 。最后 。然后初始的时候 。这里由于第二维是 的,所以这部分的时间复杂度为 。
当 为根的时候,记连通块大小为 ,那么要统计 的答案,这个东西其实不是很好统计,但是发现 的儿子很少,考虑统计 的数量。令 表示不考虑 这个儿子和 ,连通块大小为 的方案数。则答案为 。然后就是求出 。不难发现 可以表示为一个前缀和后缀的并,这个前后缀的预处理是可以 的。直接暴力合并出 的话是 的,但是发现 只有前 项有用,所以合并就变为 的了。
- 两个重心:
把 断开,然后分别以 , 为根跑一遍。然后答案为 。
时间复杂度 。
具体实现可以参考代码,感觉已经很清楚了。
Code
CPP#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define fi first
#define se second
mt19937_64 eng(time(0) ^ clock());
template <typename T>
T rnd(T l, T r) { return eng() % (r - l + 1) + l; }
const int P = 10007;
int n, rt1, rt2;
vector<int> e[205];
int sz[205], mx[205];
void DFS(int u, int ff) {
sz[u] = 1;
for (int v : e[u])
if (v != ff) {
DFS(v, u);
sz[u] += sz[v];
mx[u] = max(mx[u], sz[v]);
}
mx[u] = max(mx[u], n - sz[u]);
if (!rt1 || mx[u] < mx[rt1])
rt1 = u, rt2 = 0;
else if (mx[u] == mx[rt1])
rt2 = u;
}
int f[205][205], g[205][205], pre[205][205], suf[205][205], temp[205];
void Work(int u, int ff) {
sz[u] = 1;
int son = 0;
f[u][0] = 1;
for (int v : e[u])
if (v != ff) {
++son;
Work(v, u);
memset(temp, 0, sizeof (temp));
sz[u] += sz[v];
for (int i = 0; i <= sz[u]; i++)
for (int j = 0; j <= min(sz[v], i); j++)
(temp[i] += f[u][i - j] * f[v][j]) %= P;
memcpy(f[u], temp, sizeof (f[u]));
}
for (int i = sz[u]; i; --i) f[u][i] = f[u][i - 1];
f[u][0] = 1;
if (!son) f[u][1] = 1;
}
int ans;
void Solve(int test) {
memset(f, 0, sizeof (f)), memset(g, 0, sizeof (g)), memset(pre, 0, sizeof (pre)), memset(suf, 0, sizeof (suf)), memset(mx, 0, sizeof (mx));
rt1 = rt2 = 0;
scanf("%d", &n);
for (int i = 1, u, v; i < n; i++)
scanf("%d%d", &u, &v), e[u].emplace_back(v), e[v].emplace_back(u);
DFS(1, 0);
if (rt2) {
Work(rt1, rt2);
Work(rt2, rt1);
ans = 0;
for (int i = 1; i <= n / 2; i++)
(ans += f[rt1][i] * f[rt2][i]) %= P;
printf("Case %d: %d\n", test, ans);
for (int i = 1; i <= n; i++) e[i].clear();
return;
}
ans = 1;
Work(rt1, 0);
for (int i : e[rt1]) {
int s = 0;
for (int j = 0; j <= n; j++)
(s += f[i][j]) %= P;
(ans *= s) %= P;
}
pre[0][0] = 1;
int len = (int)e[rt1].size();
for (int i = 0; i < len; i++) {
int v = e[rt1][i];
for (int j = 0; j <= n; j++)
for (int k = 0; k <= min(sz[v], j); k++)
(pre[i + 1][j] += pre[i][j - k] * f[v][k]) %= P;
}
suf[len + 1][0] = 1;
for (int i = len - 1; i >= 0; --i) {
int v = e[rt1][i];
for (int j = 0; j <= n; j++)
for (int k = 0; k <= min(sz[v], j); k++)
(suf[i + 1][j] += suf[i + 2][j - k] * f[v][k]) %= P;
}
for (int i = 1; i <= len; i++) {
int v = e[rt1][i - 1];
for (int j = 0; j <= sz[v]; j++)
for (int k = 0; k <= j; k++)
(g[i][j] += pre[i - 1][j - k] * suf[i + 1][k]) %= P;
for (int j = 1; j <= sz[v]; j++)
(g[i][j] += g[i][j - 1]) %= P;
for (int j = 1; j <= sz[v]; j++)
(ans -= f[v][j] * g[i][j - 1]) %= P;
}
(ans += P) %= P;
printf("Case %d: %d\n", test, ans);
for (int i = 1; i <= n; i++) e[i].clear();
}
int main() {
int T;
scanf("%d", &T);
for (int i = 1; i <= T; i++)
Solve(i);
return 0;
}
相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...