AtCoder Beginner Contest 160 F.Distributing Integers(组合数学+换根dp)

题目

给出一棵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]={(sz[u]-1)!}*\prod_{v\epsilon son[u]}\frac{dp[v]}{sz[v]!}

在只考虑子树内部方案的时候,dp[u]=dp[v]的乘积,其中v是u的子树内的点,

在考虑子树间的方案带来的顺序的时,相当于把内部方案看成相同的数

比如,4个1,5个2,6个3构成一个长度为15的序列,方案有多少种,

答案是随便排的方案数除掉每个的内部顺序的方案数,\frac{15!}{4!5!6!}

第一次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

 

你可能感兴趣的:(#,树形dp/换根dp/长链剖分,换根dp,组合数学,树形dp)