zoukankan      html  css  js  c++  java
  • 二项式反演学习笔记

    二项式反演学习笔记

    二项式反演(Binomial Inversion)是一种反演,它基于容斥原理.它可以把计数问题中求解"恰好X个的方案数"转化为求解"至少X个的方案数",让问题变得更简单。

    约定:

    1. (C_n^m)表示从(n)个数里选(m)个数的方案数,等价于(inom{m}{n}).另外为了方便推式子,规定(n<m)(C_n^{m}=0)
    2. (S)是一个集合,规定(overline{S})(S)的补集
    3. (S)是一个集合,规定(|S|)(S)的元素个数
    4. ([p(x)]=egin{cases} 1,p(x) \ 0, eg p(x) end{cases})

    基本定理

    二项式反演有3种基本形式:

    [f_n = sum_{i=0}^n (-1)^i C_n^i g_i Leftrightarrow g_n = sum_{i=0}^n (-1)^i C_n^i f_i ]

    [f_n = sum_{i=0}^n C_n^i g_i Leftrightarrow g_n = sum_{i=0}^n (-1)^{n-i} C_n^i f_i ]

    [forall m geq n,f_n = sum_{i=n}^m C_i^n g_i Leftrightarrow g_n = sum_{i=n}^m (-1)^{i-n} C_i^n f_i ]

    接下来我们会证明它。

    标准形式

    定理1(二项式反演形式1):$$f_n = sum_{i=0}^n (-1)^i C_n^i g_i Leftrightarrow g_n = sum_{i=0}^n (-1)^i C_n^i f_i ag{1.1}$$

    这个式子充满了对称性,接下来我们证明它

    容斥解释

    实际上,二项式反演是容斥原理的特例。
    我们可以构造一些集合来证明这个定理,全集为(S),(S)中具有(n)种性质的集合分别为(A_1,A_2 dots A_n).特别地,我们构造这些集合,使它们满足其中任意(i)个集合的并集大小相等,记为(g_i).另外规定(g_0=|S|)

    那么根据容斥原理,均没有这些性质的集合的大小为:

    [egin{aligned}|overline{A_1} cap overline{A_2} cap cdots cap overline{A_n}|= |S| - sum |A_i| + sum |A_i cap A_j| + cdots + (-1)^n sum |A_1 cap A_2 cap cdots cap A_n| end{aligned} ]

    又因为任意(i)个集合的并集大小相等

    [egin{aligned}&|S| - sum |A_i| + sum |A_i cap A_j| + cdots + (-1)^n sum |A_1 cap A_2 cap cdots cap A_n| \ &=g_0-C_n^1 g_1+C_n^2g_2+ dots + (-1)^n C_n^n g_n end{aligned} ]

    同理可得(A_i)的补集的并集大小相等,不妨记为(f_i),同样令(f_0=|S|).那么上式可化为$f_n = sum_{i=0}^n (-1)^i C_n^i g_i $.
    再使用一次容斥原理:

    [egin{aligned} & & &|A_1 cap A_2 cap cdots cap A_n| \ &=& &|S| - sum |overline{A_i}| + sum |overline{A_i} cap overline{A_j}| \ & &+& cdots + (-1)^n sum |overline{A_1} cap overline{A_2} cap cdots cap overline{A_n}|\ &=& &f_0 - C_n^1 f_1 + C_n^2 f_2 + cdots + (-1)^n C_n^n f_n end{aligned}]

    那么$g_n = sum_{i=0}^n (-1)^i C_n^i f_i $

    这说明,两个式子是等价的。因此我们就证明了反演定理

    代数解释

    虽然容斥解释很妙,但是在题目中我们可能会遇到非标准的式子,因此要理解代数展开证明的思想。

    已知$f_n = sum_{i=0}^n (-1)^i C_n^i g_i $
    那么

    [egin{aligned}sum_{i=0}^n (-1)^i C_n^i f_i &=sum_{i=0}^n (-1)^iC_n^i sum_{j=0}^i(-1)^j C_i^j g_j \ &=sum_{j=0}^n g_j sum_{i=j}^n (-1)^{i+j}C_{n}^i C_{i}^jend{aligned} ]

    第二步是交换了求和顺序,我们尝试对于每个(f_j)计算出它的系数并提到外面,容易发现对于(j in [1,i])都会乘上系数((-1)^iC_n^i),也就是说会被(>=j)((-1)^iC_n^i)更新。
    那么这样只需要后面的和式在(j eq n)时为0,(j=n)时为1,那么总和就为(g_n)

    接下来需要用到组合数的性质

    [egin{aligned} C_n^i C_j^i &=frac{n!}{(n-i)!i!}frac{i!}{(i-j)!j!} \ &=frac{n!}{(n-i)!(i-j)!j!} \ &=frac{n!(n-j)!}{j!(n-j)!(n-i)!(i-j)!} ( ext{ 为了构造组合数,上下同乘}(n-j)!) \ &=frac{n!}{j!(n-j)!} frac{(n-j)!}{(n-i)![(n-i)-(n-j)]!} \ &= C_n^j C_{n-j}^{n-i} (1.1.2)end{aligned} ]

    代回上式

    [egin{aligned} sum_{i=j}^n (-1)^{i+j}C_{n}^i C_{i}^j &=(-1)^jC_n^j sum_{i=j}^n (-1)^i C_{n-j}^{n-i} \ &=(-1)^jC_n^j sum_{i=0}^{n-j} (-1)^{n-i} C_{n-j}^{i} ( ext{把}i ext{换成}n-i) \ &= (-1)^jC_n^j (1-1)^{n-j} ( ext{二项式定理})end{aligned} ]

    (n eq j)( ext{原式}=0)
    (n = j)时出现了(0^0)无法直接算,考虑直接用原式(因为最后一步转化破坏了等价性),(sum_{i=j}^n (-1)^{i+j}C_{n}^i C_{i}^j=sum_{i=n}^n (-1)^{i+n}C_{n}^n C_{i}^n=(-1)^{2n}C_n^nC_n^n=1)
    因此( ext{原式}=[n=j])
    也就是说

    [egin{aligned}sum_{i=0}^n (-1)^i C_n^i f_i =sum_{j=0}^n g_j [n=j]=g_nend{aligned} ]

    至多和恰好的转化

    定理2(二项式反演形式2):$$f_n = sum_{i=0}^n C_n^i g_i Leftrightarrow g_n = sum_{i=0}^n (-1)^{n-i} C_n^i f_i ag{1.2}$$

    这个式子的意义是,(f_n)表示至多(n)个的方案数,那么(g_n)就表示恰好(n)个的方案数。

    证明:
    考虑形式1

    [f_n = sum_{i=0}^n (-1)^i C_n^i g_i Leftrightarrow g_n = sum_{i=0}^n (-1)^i C_n^i f_i ]

    (g'_i=(-1)^{-i} g_i,那么f_n= sum_{i=0}^n (-1)^i C_n^i g'_i)
    那么(g'_n = sum_{i=0}^n (-1)^i C_n^i f_i)
    ((-1)^{-n}g_n=sum_{i=0}^n (-1)^i C_n^i f_i)
    两边同乘((-1)^{-2n}=1),得到$g_n=sum_{i=0}^n (-1)^{i-n} C_n^i f_i=sum_{i=0}^n (-1)^{n-i} C_n^i ( ) (forall x in mathbb{Z},(-1)^{-x}=(-1)^{2x}(-1)^{-x}=(-1)^x)$

    至少和恰好的转化

    这是最常见的二项式反演形式,一定要掌握!

    定理3(二项式反演形式3):$$f_n = sum_{i=n}^m C_i^n g_i Leftrightarrow g_n = sum_{i=n}^m (-1)^{i-n} C_i^n f_i ag{1.3}$$
    对于(forall m geq n),结论都成立,即使是一个无限求和也可以

    这个式子的意义是,(f_n)表示 "至少" (n)个的方案数,那么(g_n)就表示恰好(n)个的方案数.这里的 "至少"打引号是因为它不是一个简单的后缀和,它表示我们"钦定"(n)个数满足条件,剩下的数不一定满足条件(f)的时候每个(g_i)是会被重复计算的,计算了(C_n^i)次.

    证明:

    [egin{aligned} sum_{i=n}^m (-1)^{i-n} C_i^n f_i &= sum_{i=n}^m (-1)^{i-n} C_i^n sum_{j=i}^m C_j^i f_jend{aligned} ]

    ((1.1))的证明,交换求和顺序

    [egin{aligned} ext{原式} &= sum_{j=n}^m f_j sum_{i=n}^j (-1)^{i-n} C_{j}^i C_i^nend{aligned} ]

    注意到后面的(sum_{i=n}^j (-1)^{i-n} C_{j}^i C_i^n)部分也很相似,因此可以用类似的方法处理。
    代入组合数的性质((1.1.2))

    [egin{aligned} sum_{i=n}^j (-1)^{i-n} C_{j}^i C_i^n &= sum_{i=n}^k C_j^n C_{j-n}^{j-i} \ &= (-1)^{-n}C_n^j sum_{i=n}^j (-1)^i C_{j-n}^{j-i} \ &= (-1)^{-n}C_n^j sum_{i=0}^{j-n} (-1)^{j-i} C_{j-n}^{i} \ &= (-1)^{-n}C_n^j (1-1)^{j-n} \&=[n=j] end{aligned} ]

    容易发现这个证明过程只是把原来的(n-j)换成(j-n)而已。

    *广义的反演

    我们发现形式(1)和形式(3)的证明有相似之处,都是交换求和顺序然后提取出和式,证明它等于([n=k]). 我们不妨将这个方法推广到任意序列。

    定理4
    命题$$f_n = sum_{i=0}^n a_{n,i} g_i Leftrightarrow g_n = sum_{i=0}^n b_{n,i} f_i $$成立的充要条件是

    [forall i in mathbb{N},sum_{j=i}^n b_{n,j}a_{j,i}=[n=i] ]

    证明:
    还是交换求和顺序

    [sum_{i=0}^n b_{n,i} f_i=sum_{i=0}^n b_{n,i} sum_{j=0}^i a_{i,j}g_j=sum_{j=1}^n g_j sum_{i=j}^n b_{n,j} a_{i,j} ]

    把字母换一下就得到了上面的定理

    应用

    在做题中,看到求"恰好X"的方案数,就要敏感的想到二项式反演。

    形式1几乎没有什么用

    形式2(至多)的应用

    错排问题

    求错排问题的通项公式:
    若一个排列中所有的元素都不在自己原来的位置上,那么这样的排列就称为原排列的一个错排。 (n)个元素的错排数记为(D_n)

    [D_n=n!sum_{i=0}^n frac{(-1)^i}{i!} ]

    其实可以把(D_n)看成不在原来位置上的元素有(n)个的排列数。设(f_n)表示长度为n的排列,在原来位置上的元素至多有(n)个的排列数,显然所有排列都满足这个性质,即(f_n=n!)

    那么

    [egin{aligned} sum_{i=0}^n (-1)^{n-i} C_n^i f_i &= sum_{i=0}^n (-1)^{n-i} frac{n!}{i!(n-i)!}i! \ &=sum{i=0}^n n! frac{(-1)^{n-i}}{(n-i)!} \ &= n!sum_{i=0}^n frac{(-1)^i}{i!}end{aligned} ]

    min-max容斥

    (S)为一个集合,(max(S),min(S))分别表示集合中的最大值和最小值。特别地,规定(max(emptyset)=min(emptyset)=0),则$$max(S)=sum_{Tsubset S}(-1)^{|T|-1} min(T)$$

    证明:
    设存在一个以集合大小为自变量的函数(f)满足

    [max(S)=sum_{Tsubset S}f(|T|) min(T) ]

    (S)中元素从大到小分别为(a_1,a_2 dots a_{|S|}).
    那么(a_i)对等式左边的贡献是([i=1]),因为(a_1=max(S)).
    (a_i)对等式右边的贡献为(sum_{j=0}^{i-1}C_{i-1}^j f(j+1)). 这是因为要(i)成为大小为(j+1)的集合的集合中的最小值,就要从(a_1,a_2 dots a_{i-1})这些比(i)大的数中选出(j)个,再加上(a_i)构成一个集合。

    那么为了等式成立,左右的贡献应该两两抵消,只剩下(a_1).所以必有

    [[i=1]=sum_{j=0}^{i-1}C_{i-1}^j f(j+1) ]

    构造函数(A(x)=[x=0]),那么(A(x-1)=[x=1]). 注意到右边有(f(j+1)),不方便反演,不妨令(B(x)=f(x+1))

    那么(A(i)=sum_{j=0}^i C_i^j B(j))
    反演得到(B(i)=sum_{j=0}^i (-1)^{n-j} C_i^jA(j)=sum_{j=0}^i (-1)^{n-j} C_i^j[j=0]=(-1)^{i})

    所以(f(x)=B(x-1)=(-1)^{x-1}),代回原式,得

    [max(S)=sum_{Tsubset S}(-1)^{|T|-1} min(T) ]

    min-max容斥有许多应用,这里不再赘述。

    形式3(至少)的应用

    由于形式3的(f)定义是可以有重复计数的,因此它会比"恰好"的形式好求,因为不需要考虑计数时的一些判重问题。这也给求(f)的过程(如直接数学计算/DP)提供了方便。

    [BZOJ2839] 集合计数
    一个有N个元素的集合有(2^N)个不同子集(包含空集),现在要在这(2^N)个集合中取出若干集合(至少一个),使得
    它们的交集的元素个数为K,求取法的方案数,答案模1000000007。

    (f_i)表示交集的元素个数 "至少"(i)的方案数。那么我们可以从(n)个元素中选出(i)个钦定为交集。剩下的(n-i)个元素组成(2^{n-i})包含空集的集合。从这些集合中任意选一些集合并上那(i)个数,它们的交集一定是这(i)个数。
    因此

    [f_i=C_n^i (2^{2^{n-i}}-1) ]

    根据二项式反演,答案为

    [sum_{i=k}^n (-1)^{i-k}C_{i}^kf_i ]

    注意(2^{2^{n-i}})无法快速幂,只需要递推求即可。因为(2^{2^i}=2^{2^{i-1}} cdot2^{2^{i-1}})

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define mod 1000000007
    #define maxn 1000000
    using namespace std;
    typedef long long ll;
    inline ll fast_pow(ll x,ll k){
    	ll ans=1;
    	while(k){
    		if(k&1) ans=ans*x%mod;
    		x=x*x%mod;
    		k>>=1;
    	}
    	return ans;
    }
    inline ll inv(ll x){
    	return fast_pow(x,mod-2);
    }
    ll fact[maxn+5],invfact[maxn+5];
    void ini(int n){
    	fact[0]=1;
    	for(int i=1;i<=n;i++) fact[i]=fact[i-1]*i%mod;
    	invfact[n]=inv(fact[n]);
    	for(int i=n-1;i>=0;i--) invfact[i]=invfact[i+1]*(i+1)%mod;
    }
    ll C(int n,int m){
    	return fact[n]*invfact[n-m]%mod*invfact[m]%mod;
    } 
    
    int n,k;
    ll f[maxn+5],g[maxn+5];
    int main(){
    	scanf("%d %d",&n,&k);
    	ini(n);
    	ll pw=2;//2^(2^(n-i)),无法快速幂计算,只能递推 
    	for(int i=n;i>=0;i--){
    		f[i]=C(n,i)*(pw-1)%mod;
    		pw=pw*pw%mod;
    	}
    	ll ans=0;
    	for(int i=k;i<=n;i++){
    		if((i-k)%2==1) ans=ans-C(i,k)*f[i]%mod;
    		else ans=ans+C(i,k)*f[i]%mod;
    		ans=(ans+mod)%mod;
    	}
    	printf("%lld
    ",ans);
    }
    
    

    [LuoguP4859]已经没有什么好害怕的了
    (这里是简化版的题意)给出两个长度为(n)的序列(a,b),现在我们要把每个(a)中元素与每个(b)中元素配对,问满足(a_x>b_y)的配对数比(a_x<b_y)的配对数多(k)的配对方案数

    首先问题转化为(a>b)的个数有(frac{n+k}{2})

    (f(i))表示保证(钦定)有(i)(a>b),其余不一定。(g(i))表示恰好(i)对a>b.显然(f(i)=sum_{j=i}^m C_j^i g(j)),根据二项式反演定理(g(i)=sum_{j=i}^m C_{j}^i f(j))

    考虑如何求(f).把(a)从小到大排序,记(cnt(i))表示(b)中小于(a_i)的数的个数。设(dp_{i,j})(a)序列的前(i)个,保证有(j)(a>b)的方案数,则(f_i=dp_{n,i}(n-i)!)

    容易写出转移方程:
    (dp_{i,j}=dp_{i,j-1}+dp_{i-1,j-1}cdot (cnt(i)-(j-1)))

    我们按(a)排序,因此(b)中小于(a_i)的数的集合一定包含(b)中小于(a_{i-1})的数的集合.之前已经选了(j-1)个数,那只能选剩下的(cnt(i)-(j-1))个数来使得(a_i>b_x),这样对数会增加1。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define mod 1000000009
    #define maxn 2000
    using namespace std;
    typedef long long ll;
    inline ll fast_pow(ll x,ll k){
    	ll ans=1;
    	while(k){
    		if(k&1) ans=ans*x%mod;
    		x=x*x%mod;
    		k>>=1;
    	}
    	return ans;
    }
    inline ll inv(ll x){
    	return fast_pow(x,mod-2);
    }
    ll fact[maxn+5],invfact[maxn+5];
    void ini(int n){
    	fact[0]=1;
    	for(int i=1;i<=n;i++) fact[i]=fact[i-1]*i%mod;
    	invfact[n]=inv(fact[n]);
    	for(int i=n-1;i>=0;i--) invfact[i]=invfact[i+1]*(i+1)%mod;
    }
    ll C(int n,int m){
    	return fact[n]*invfact[n-m]%mod*invfact[m]%mod;
    } 
    
    int n,k;
    int a[maxn+5],b[maxn+5];
    int cnt[maxn+5];//a从小到大排序后,a[i]>b[j]的j的数量 
    ll dp[maxn+5][maxn+5];
    ll f[maxn+5],g[maxn+5];
    
    void calc_inv(ll *f,ll *g,int n){
    	for(int i=0;i<=n;i++){
    		for(int j=i;j<=n;j++){
    			if((j-i)%2==1) g[i]-=C(j,i)*f[j]%mod;
    			else g[i]+=C(j,i)*f[j]%mod;
    			g[i]=(g[i]+mod)%mod;
    		}
    	}
    } 
    int main(){
    	scanf("%d %d",&n,&k);
    	ini(n);
    	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    	for(int i=1;i<=n;i++) scanf("%d",&b[i]);
    	sort(a+1,a+1+n);
    	for(int i=1;i<=n;i++){
    		for(int j=1;j<=n;j++) if(a[i]>b[j]) cnt[i]++;
    	}
    	for(int i=0;i<=n;i++) dp[i][0]=1;
    	for(int i=1;i<=n;i++){
    		for(int j=1;j<=n;j++){
    			dp[i][j]=(dp[i-1][j-1]*max(cnt[i]-j+1,0)%mod+dp[i-1][j])%mod;
    		}
    	}
    	for(int i=0;i<=n;i++) f[i]=dp[n][i]*fact[n-i]%mod;
    	calc_inv(f,g,n);
    	printf("%lld
    ",g[(n+k)/2]);
    } 
    

    [NOIOnline2提高组T3]游戏
    小 A 和小 B 正在玩一个游戏:有一棵包含 n=2m个点的有根树(点从1∼n 编号),它的根是 1 号点,初始时两人各拥有 m 个点。游戏的每个回合两人都需要选出一个自己拥有且之前未被选过的点,若对手的点在自己的点的子树内,则该回合自己获胜;若自己的点在对方的点的子树内,该回合自己失败;其他情况视为平局。游戏共进行 m 回合。
    对于 k=0,1,2,⋯,m,计算出非平局回合数为 k 的情况数。两种情况不同当且仅当存在一个小 A 拥有的点 x,小 B 在 x 被小 A 选择的那个回合所选择的点不同。

    二项式反演的套路,设(f(i))为非平局回合数至少为(i)的情况. (g(i))为非平局回合数恰好为(i)的情况,则(f(i)=sum_{j=i}^m C_j^i g(j)).(这里的"至少"指的是我们"钦定"有(i)局一定平局,其他不一定,所以至少为(i).因此对(f(i))求后缀和不是(g(i)),会算重)我们要求的是(g(k)),但是(g)不太好求,考虑求(f)再反演得出(g).根据二项式反演定理(g(i)=sum_{j=i}^m C_{j}^i f(j))

    (f)可以用树形背包来求。(dp_{x,i})(x)的子树内至少(i)对选择的有祖先-后代关系的点(即非平局的回合).那么(f(i)=dp_{1,i}cdot(m-i)!)(剩下的(m-i)对关系可以任意排序)

    DP的时候先用模板的树形背包合并子树,然后考虑(x)这个点对答案的影响,有
    (dp_{x,i} leftarrow dp_{x,i}+dp_{x,i-1}cdots( ext{x子树内不被x的拥有者拥有的节点个数}-(i-1)))
    这是因为(x)可以和子树内不被(x)的拥有者拥有的,且还没被配对(减去(i-1))的节点形成非平局回合。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #define mod 998244353
    #define maxn 5000
    using namespace std;
    typedef long long ll;
    inline ll fast_pow(ll x,ll k){
    	ll ans=1;
    	while(k){
    		if(k&1) ans=ans*x%mod;
    		x=x*x%mod;
    		k>>=1;
    	}
    	return ans;
    }
    inline ll inv(ll x){
    	return fast_pow(x,mod-2);
    }
    ll fact[maxn+5],invfact[maxn+5];
    void ini(int n){
    	fact[0]=1;
    	for(int i=1;i<=n;i++) fact[i]=fact[i-1]*i%mod;
    	invfact[n]=inv(fact[n]);
    	for(int i=n-1;i>=0;i--) invfact[i]=invfact[i+1]*(i+1)%mod;
    }
    ll C(int n,int m){
    	return fact[n]*invfact[n-m]%mod*invfact[m]%mod;
    } 
    
    int m,n;
    int a[maxn+5];
    struct edge {
    	int from;
    	int to;
    	int next;
    } E[maxn*2+5];
    int head[maxn+5];
    int esz=1;
    void add_edge(int u,int v) {
    	esz++;
    	E[esz].from=u;
    	E[esz].to=v;
    	E[esz].next=head[u];
    	head[u]=esz;
    }
    
    int sz[maxn+5];
    int cnt[maxn+5][2];
    ll dp[maxn+5][maxn+5];//x的子树内,至少有i对祖先关系的方案数(有i对我们已经钦定,剩下的随便搞 
    //在dp转移中,我们钦定i对关系来转移,最后再乘上(m-i)!,表示剩下的关系可以任意匹配 
    //因为只是钦定,转移的时候就不需要考虑剩下的关系。而如果直接定义状态为恰好i对,转移时无法保证合法 
    void dfs(int x,int fa) {
    	static ll tmp[maxn+5];
    	sz[x]=1;
    	cnt[x][a[x]]++;
    	dp[x][0]=1;
    	for(int i=head[x];i;i=E[i].next){//先合并子树 
    		int y=E[i].to;
    		if(y!=fa){
    			dfs(y,x);
    			for(int j=0;j<=sz[x]+sz[y];j++) tmp[j]=0;
    			for(int j=0;j<=sz[x];j++){
    				for(int k=0;k<=sz[y];k++){
    					tmp[j+k]=(tmp[j+k]+dp[x][j]*dp[y][k]%mod)%mod;
    				}
    			}
    			for(int j=0;j<=sz[x]+sz[y];j++) dp[x][j]=tmp[j];
    			sz[x]+=sz[y];
    			cnt[x][0]+=cnt[y][0];
    			cnt[x][1]+=cnt[y][1];
    		}
    	}
    	//计算x的贡献 
    	for(int i=cnt[x][a[x]^1];i>=1;i--){//对于dp[x][i],x可以和子树里还没被选的与a[x]颜色相反的点匹配 
    		//于是贡献为dp[x][i-1]*(cnt[x][a[x]^1]-(i-1)) 倒序是为了防止重复更新 
    		dp[x][i]+=dp[x][i-1]*(cnt[x][a[x]^1]-(i-1))%mod;
    		dp[x][i]%=mod;
    	}
    }
    
    ll f[maxn+5],g[maxn+5];
    int main() {
    	int u,v;
    	scanf("%d",&n);
    	m=n/2;
    	ini(n);
    	for(int i=1;i<=n;i++) scanf("%1d",&a[i]);
    	for(int i=1;i<n;i++){
    		scanf("%d %d",&u,&v);
    		add_edge(u,v);
    		add_edge(v,u);
    	}
    	dfs(1,0);
    	for(int i=0;i<=m;i++) f[i]=dp[1][i]*fact[m-i]%mod;//求出真正的dp值
    	for(int i=0;i<=m;i++){
    		for(int j=i;j<=m;j++){
    			if((j-i)%2==1) g[i]-=C(j,i)*f[j]%mod;
    			else g[i]+=C(j,i)*f[j]%mod;
    			g[i]=(g[i]+mod)%mod;
    		}
    		printf("%lld
    ",g[i]);
    	} 
    }
    
  • 相关阅读:
    Jmeter之http性能测试实战 非GUI模式压测 NON-GUI模式 结果解析TPS——干货(十一)
    UI Recorder 自动化测试 回归原理(九)
    UI Recorder 自动化测试 录制原理(八)
    UI Recorder 自动化测试 整体架构(七)
    UI Recorder 自动化测试 配置项(六)
    UI Recorder 自动化测试 工具栏使用(五)
    UI Recorder 自动化测试 回归测试(四)
    UI Recorder 自动化测试 录制(三)
    UI Recorder 自动化测试工具安装问题疑难杂症解决(二)
    UI Recorder 自动化测试安装教程(一)
  • 原文地址:https://www.cnblogs.com/birchtree/p/12811308.html
Copyright © 2011-2022 走看看