• 二项式反演学习笔记


    二项式反演学习笔记

    二项式反演(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]);
    	} 
    }
    
  • 相关阅读:
    P1631-序列合并
    P1484-种树
    17.树的子结构(python)
    16.合并两个排序的链表(python)
    反转链表
    链表中倒数第k个节点(python)
    调整数组顺序使奇数位于偶数前面(python)
    Spark--wordcount(词频降序)
    数值的整数次方
    二进制中1的个数(python)
  • 原文地址:https://www.cnblogs.com/birchtree/p/12811308.html
Copyright © 2020-2023  润新知