20251118【NOIP】模拟 C. 修建道路
20251118【NOIP】模拟 C. 修建道路
简化题意:
一棵树,任意加边,使新图恰有
关键观察:
- 将所有割边断开,会形成恰好
k 个连通分量。 - 所有的割边都是原来的树边。
考虑结论 2 的证明:
- 考虑新加入的边,一定不会有新割边产生,因为两个端点一定在原树上是连通的。
所以可以转化,新问题为:
一棵树,选
这个问题看起来还是很困难,因为我们无法对连通块内的合法方案数计数,所以考虑二项式反演。
二项式反演:
我有
设
有关系
回到原问题,我们使用二项式反演,将原问题转化为:
- 一棵树,选
k 个边作为割边,在剩下连通块里任意加边的方案数。
同时我们知道在一个大小为
所以如果我们知道了那些边被割掉,形成了一些大小为
所以可以树形 dp。
设
转移,考虑将
这是把当前这条边割去了,此时以
此时是不割,继续向上递归。
这个复杂度是
考虑多项式,首先设
但复杂度并没有变化,注意到这个复杂度瓶颈在于遍历数组中的每一个数。,所以使用另一个公式:拉格朗日插值
根据这个,我们知道了只用
然后就是板子,使用
代码(赵队的):
#include<bits/stdc++.h>
#define int long long
using namespace std;
int get() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)) { if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)) { x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
const int N = 705, P = 1e9 + 7, sgn[] = { 1ll, P - 1 };
int n, fac[N], inv[N], ans[N];
struct Edge { int v, nxt; } edge[N << 1];
int head[N], tot;
int f[N][N], sze[N], a[N][N], g[N], tmp[N], x, C[N][N];
int qpow(int x, int y) {
int res = 1;
while(y) res = res * ((y & 1)? x : 1) % P, x = x * x % P, y >>= 1;
return res;
}
void add(int u, int v) { edge[++tot] = (Edge){ v, head[u] }, head[u] = tot; }
void inc(int &x, int y) { x = (x + y) % P; }
void dec(int &x, int y) { x = ((x - y) % P + P) % P; }
int S(int n) { return qpow(2, n * (n - 1) / 2 - (n - 1)); }
void init(int n) {
fac[0] = 1;
for(int i = 1; i <= n; i++) fac[i] = fac[i - 1] * i % P;
inv[n] = qpow(fac[n], P - 2);
for(int i = n; i >= 1; i--) inv[i - 1] = inv[i] * i % P;
for(int i = 1; i <= n; i++) g[i] = S(i);
for(int i = 0; i <= n; i++) {
C[i][0] = 1;
for(int j = 1; j <= i; j++) C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % P;
}
}
void dfs(int u, int lst) {
f[u][1] = 1, sze[u] = 1;
for(int i = head[u]; i; i = edge[i].nxt) {
int v = edge[i].v;
if(v == lst) continue;
dfs(v, u);
for(int a = 1; a <= sze[u]; a++)
for(int b = 1; b <= sze[v]; b++) {
// 不连接
inc(tmp[a], f[u][a] * f[v][b] % P * g[b] % P * x);
// 连接
inc(tmp[a + b], f[u][a] * f[v][b]);
}
for(int i = 1; i <= sze[u] + sze[v]; i++) f[u][i] = tmp[i], tmp[i] = 0;
sze[u] += sze[v];
}
}
void Gauss_Jordan(int n) {
for(int i = 1; i <= n; i++) {
int mp = i;
for(int j = i + 1; j <= n; j++) if(a[j][i] > a[mp][i]) mp = j;
for(int j = 1; j <= n + 1; j++) swap(a[i][j], a[mp][j]);
int t = qpow(a[i][i], P - 2);
for(int j = 1; j <= n + 1; j++) a[i][j] = a[i][j] * t % P;
for(int j = 1; j <= n; j++) {
if(j == i || !a[j][i]) continue;
int t = a[j][i];
for(int k = 1; k <= n + 1; k++) dec(a[j][k], a[i][k] * t);
}
}
}
main() {
n = get();
for(int i = 1, u, v; i < n; i++)
u = get(), v = get(), add(u, v), add(v, u);
init(n);
for(int i = 1; i <= n + 1; i++) {
memset(f, 0, sizeof(f)), x = i;
dfs(1, 0);
for(int j = 1; j <= n; j++) inc(a[i][n + 2], f[1][j] * g[j] % P * x);
for(int j = 1; j <= n + 1; j++) a[i][j] = qpow(x, j - 1);
}
Gauss_Jordan(n + 1);
for(int i = 1; i <= n; i++) ans[i] = a[i + 1][n + 2];
for(int i = 1; i <= n; i++)
for(int j = i + 1; j <= n; j++) inc(ans[i], sgn[(j - i) & 1] * ans[j] % P * C[j - 1][i - 1]);
for(int i = 1; i <= n; i++) cout << ans[i] << " ";
return 0;
}
/*
3
1 2
1 3
*/