题目分析
这大概率是一道dp题,但是由于树形结构的特殊性,使得以点的序号作为阶段进行转移不再合适,再基于题干中的描述 对于1至N的每个k进行如下操作 ,启发我们先求出以一个点作为序列第一个元素的方案,随后进行转移。
那么我们先分析对于一个定点作为根节点的情况。
(1)如果正向的考虑这个问题,先求出每棵子树的状态再考虑合并,情况将类似于将两个有序序列合并成一个序列,并对于各个原序列而言元素顺序不变,这要求很多的排列组合与递归函数,如利用
G
x
(
a
,
b
)
Gx(a, b)
Gx(a,b) 来计算分别将两个长度为
a
,
b
a,b
a,b 的序列合并所可能的方案,
G
x
(
a
,
b
)
Gx(a, b)
Gx(a,b) 需要转化为
G
x
(
a
,
b
−
1
)
+
G
x
(
a
−
1
,
b
)
Gx(a, b - 1) + Gx(a - 1, b)
Gx(a,b−1)+Gx(a−1,b) 递归求解,在本题背景下,即
O
(
2
×
1
0
5
)
O(2 \times10^5)
O(2×105) 规模下这是无法做到的。
(2)因此,我们反向思考问题。对于总计 n ! n! n! 种忽视合法性的方案,分别对每棵子树考虑:由于该树根节点必须在子节点被选中前先被选中,因此必须是第一个此子树中被选中的,因此对于以 u u u 为根的子树而言,只有 n ! s i z [ u ] \frac{n!}{siz[u]} siz[u]n! 种方案是满足要求的,并且合法的方案对剩余未考虑的子树而言仍然可能是不合法的,反复利用这一性质,我们得到最终有 n ! ∏ i = 1 n s i z [ i ] \frac{n!}{\prod_{i=1}^{n} siz[i]} ∏i=1nsiz[i]n! 种方案是合法的。考虑到取模,我们要使用逆元进行求逆的运算。
那现在考虑转移就更为简单了,只需用换根dp不断设新节点作为根节点,并改变影响的 s i z siz siz 值,改变连乘值,最后回溯修改回原状态即可。由于一次只涉及两个 s i z siz siz 值的修改(原根和现根),换根的时间复杂度被认为是 O ( 1 ) O(1) O(1) , 再考虑上快速幂,总时间复杂度在 O ( n l o g P ) O(nlogP) O(nlogP) 级别,P为取模数。
代码
#include <iostream>
#include <vector>
using i64 = long long;
const int MAXN = 2e5 + 5, P = 1e9 + 7;
int n;
i64 tot = 1, prod = 1, ans[MAXN], siz[MAXN];
std::vector<int> G[MAXN];
void Dfs1(int u, int f){
++ siz[u];
for(int v : G[u]){
if(v == f){
continue;
}
Dfs1(v, u);
siz[u] += siz[v];
}
prod *= siz[u];
prod %= P;
return;
}
i64 myPow(i64 a, i64 b){//a^b
if(b == 0){
return 1;
}
i64 ret = myPow(a, b / 2) % P;
if(b % 2 == 0){
return (ret * ret) % P;
}else{
return (((ret * ret) % P) * a) % P;
}
}
void Dfs2(int u, int f){
ans[u] = (myPow(prod % P, P - 2) * tot) % P;
for(int v : G[u]){
if(v == f){
continue;
}
//将v重置为根节点,需要重置siz[u],siz[v],prod的值
prod = (myPow((siz[u] * siz[v]) % P, P-2) * prod) % P;
i64 c = siz[u];
siz[u] -= siz[v];
siz[v] = c;
prod *= siz[u];
prod %= P;
prod *= siz[v];
prod %= P;
Dfs2(v, u);
//将u重置为根节点,需要重置siz[u],siz[v],prod的值
prod = (myPow((siz[u] * siz[v]) % P, P - 2) * prod) % P;
siz[v] -= siz[u];
siz[u] = c;
prod *= siz[u];
prod %= P;
prod *= siz[v];
prod %= P;
}
return;
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
std::cin >> n;
for(i64 i = 1; i < n; ++ i){
int a,b;
std::cin >> a >> b;
G[a].push_back(b);
G[b].push_back(a);
tot *= (i + 1);
tot %= P;
}
Dfs1(1, 1);
Dfs2(1, 1);
for(int i = 1; i <= n; ++ i){
std::cout << ans[i] << "\n";
}
return 0;
}
补充
这种形式的问题被称为拓扑序个数问题

被折叠的 条评论
为什么被折叠?



