给出一棵n(2<=n<=1e5)个点的树。
对于k从[1,n],分别解决如下问题:
考虑按照如下的顺序在树上的每一个点上写数字:
①在点k上写下数字1
②已经写数的点可以向相邻点扩展,扩展到一个还没写数的点,在该点上写一个还没写过的且最小的数,
如果有多个可以扩展,随机选取一个
③重复②过程,直至写完2到n
对于每一个k,求不同的写数方案,答案模1e9+7
Codeforces群winterzz1 + Atcoder官方题解
换根dp,一般用于sz[u]、sz[v]发生改变时,可以通过相邻的状态转移而来
把暴力的O(n^2)的树形dp,转化为O(n)的树形dp
两遍dfs,第一遍先求根的答案,第二遍通过换根求出所有点的答案
在只考虑子树内部方案的时候,dp[u]=dp[v]的乘积,其中v是u的子树内的点,
在考虑子树间的方案带来的顺序的时,相当于把内部方案看成相同的数
比如,4个1,5个2,6个3构成一个长度为15的序列,方案有多少种,
第一次dfs,回溯的时候,dp[1]的答案有了,
第二次dfs的时候,起始u是v的父亲,切断u和v之间的连边,然后把u挂到v的子树上,起到了换根的过程,
注意回溯的时候,再把根换回来
每次sz发生变化时,先除掉原来的,再乘上一个新的,
本题为了快速转移,要预处理阶乘、阶乘的逆元
#include
using namespace std;
typedef long long ll;
#define pb push_back
const int N=2e5+10,mod=1e9+7;
vectore[N];
int sz[N],n,u,v;
ll dp[N],fac[N],Finv[N],inv[N],ans[N];
ll modpow(ll x,ll n,ll mod)
{
ll res=1;
for(;n;x=x*x%mod,n/=2)
if(n&1)res=res*x%mod;
return res;
}
ll get_inv(ll x)
{
return modpow(x,mod-2,mod);
}
void init(int n)//n=1;--i)Finv[i]=Finv[i+1]*(i+1)%mod;
}
//视具体情况更改link/cut/leaf函数
void dp_link(int rt1,int rt2)
{
//因sz发生变化 除掉原来的(sz-1)! 乘上新的(sz-1)!
dp[rt1]=dp[rt1]*Finv[sz[rt1]-1]%mod;
sz[rt1]+=sz[rt2];
dp[rt1]=dp[rt1]*fac[sz[rt1]-1]%mod;
//乘上dp[rt2]/(sz[rt2]!)
dp[rt1]=dp[rt1]*Finv[sz[rt2]]%mod;
dp[rt1]=dp[rt1]*dp[rt2]%mod;
}
void dp_cut(int rt1,int rt2)
{
//因sz发生变化 除掉原来的(sz-1)! 乘上新的(sz-1)!
dp[rt1]=dp[rt1]*Finv[sz[rt1]-1]%mod;
sz[rt1]-=sz[rt2];
dp[rt1]=dp[rt1]*fac[sz[rt1]-1]%mod;
//除掉dp[rt2]/(sz[rt2!]
dp[rt1]=dp[rt1]*fac[sz[rt2]]%mod;
dp[rt1]=dp[rt1]*get_inv(dp[rt2])%mod;
}
void get_leaf(int rt)
{
sz[rt]=1;
dp[rt]=1;
}
///-----------------------------
void change_rt(int rt1,int rt2)//把根从rt1切换到rt2
{
dp_cut(rt1,rt2);// 使rt1失去儿子rt2
dp_link(rt2,rt1);//使rt2加上儿子rt1
}
void dp_dfs(int x,int fa)
{
get_leaf(x);
for(auto &i:e[x])
{
if(i!=fa)
{
dp_dfs(i,x);
dp_link(x,i);//回溯时 把x挂到i上
}
}
return;
}
void dfs(int x,int fa)
{
ans[x]=dp[x];
for(auto &i:e[x])
{
if(i!=fa)
{
change_rt(x,i);//根从x换到i
dfs(i,x);
change_rt(i,x);//回溯 把根换回来
}
}
return;
}
int main()
{
scanf("%d",&n);
init(200000);
for(int i=1;i