10.11 組隊賽補題
ZOJ-4069
解題思路:
1.n個點組成環的不同種類數是(n-1)!/2;n個點組成一條鏈的不同種類數是n!/2,特別的n==1時種類數為1。
用指數型生成函式表示k個點形成鏈的種類: 1/2(2x+2!x^2/2!+3!x^3/3!+4!x^4/4!+..+n!x^n/n!) = 1/2(2*x+x^2+x^3+x^4+..+x^n) = S
xS = 1/2(2x^2+x^3+x^4+..+x^(n+1)),xS - S = 1/2(x^2+x^(n+1)-2x),S = 1/2(x^2+x^(n+1)-2x)/(x-1) = 1/2x*(x+x^n-2)/(x-1)。
當|x|<1時,n->∞時,x^n->0,S = 1/2x
同理用上訴方法當|x|<1,n->∞時可以求得1+x+x^2+x^3+...+x^n = 1/(1-x)。(這個不就是上面式子的分母嗎?下面會說他的作用)
2.如果在n個點中只給你m條邊,那麼最後形成的圖肯定是由n-m條鏈組成的,令k = n - m。
3.那麼要求n個點組成k條鏈的不同方案數就是求S^k = (x(1-x/2)/(1-x))^k的多項式展開的第n項的係數,也就是a[n]x^n/n!,a[n]就是我們要求的,不過最後還要除以k!,因為k條鏈不需要分先後順序。
4.S^k = (x(1-x/2)/(1-x))^k = x^k(1-x/2)^k*(1/(1-x))^k,此處(1-x/2)^k可以用二項式展開,那麼1/(1-x)該如何處理呢,這就用到了上面已經求出的1/(1-x)的多項式,這個多項式所以係數都是1,那麼與此多項式相乘得到第n項的係數不就是原多項式的0到n項係數的和嗎?
所以(1-x/2)^k的多項式乘一次1/(1-x)得到新的係數,就是求n前係數字首和,乘k次就是求k次。
5.在k次求字首和中我們可以直接算出原多項式((1-x/2)^k)的每一項係數對最終結果的貢獻。此處列舉第1項的係數的貢獻T,當k=1時顯然T = 1,k = 2時此時所有1到n的項第1項都加上了一次,所以T = k,這個結果就可以想到用組合求解。所以對於求k次字首和之後原第i項對最終第j項(j>=i)的貢獻次數是C(j-i+k-1,k-1)。可以用(C(m,m)+C(m+1,m)+C(m+2,m)+C(m+3,m)+...+C(n,m)==C(n+1,m+1))去推。
6.最後記得乘上(1-x/2)^k上的-1/2的冪啊!求個逆元就行了,還有此多項式的第i項係數就是C(k,i)*(-1/2)^i。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int mx = 1e5 + 10;
ll fac[mx],inv[mx];
ll n ,m;
void init()
{
inv[0] = fac[0] = inv[1] = 1;
for(int i=1;i<mx;i++) fac[i] = fac[i-1]*i%mod;
for(int i = 2;i<mx;++i) inv[i] = (mod-mod/i)*inv[mod%i] % mod;
for(int i=2;i<mx;i++) inv[i] = inv[i]*inv[i-1]%mod;
}
ll C(int x,int y)
{
return fac[x]*inv[y]%mod*inv[x-y]%mod;
}
int main()
{
int t;
init();
scanf("%d",&t);
while(t--){
scanf("%lld%lld",&n,&m);
if(m>n){
puts("0");
continue;
}
if(m==n){
printf("%lld\n",fac[n-1]*inv[2]%mod);
continue;
}
ll ans = 0,base = 1;
int k = n - m,cnt = min(m,n-m);
for(int i=0;i<=cnt;i++){
ans = (ans + C(k,i)*base%mod*C(m-i+k-1,k-1))%mod;
base = base * -inv[2]%mod;
}
ans = (ans+mod)%mod;
printf("%lld\n",ans*fac[n]%mod*inv[n-m]%mod);
}
return 0;
}