1. 程式人生 > 實用技巧 >P4071 [SDOI2016]排列計數 錯排+組合數

P4071 [SDOI2016]排列計數 錯排+組合數

題意:

求有多少種 \(1\)\(n\) 的排列 \(a\),滿足序列恰好有 \(m\) 個位置 ii,使得 \(a_i=i\),\(T\)組詢問

答案對\(10^9+7\) 取模。

範圍&性質:\(1\le T\le 10^5,1\le n,m\le10^6\)

分析:

前置芝士:

錯排的常見公式:

\(f_i\)表示 \(i\) 階排列的個數,\(g_i\) 表示 \(i\) 階錯排的個數。
\(n! = f_n =n \sum C_n^igi\)
於是可以解得
\(g_i =n \sum (−1)^{n−i}C_n^i\ fi =n \sum (−1)^{n−i}C_n^i i! = n!\sum_{i=0}^n \frac{(-1)^i}{i!}\)


由此還可以順便求出它的遞推式是:

\(g_{n+1}= (n + 1)g_n + (−1)^{n+1}\) 其初值是 \(g_0 = 1\)


對於這道題來說,先組合數從n個數中選擇m個,再乘上錯排

程式碼:

#include<bits/stdc++.h>

using namespace std;

namespace zzc
{
	const int mod = 1e9+7;
	const int maxn = 1e6+5; 
	int t,n,m;
	long long inv[maxn],fac[maxn],per[maxn];
	
	void init()
	{
		inv[1]=1;
		fac[1]=1;
		fac[0]=1;
		for(int i=2;i<=1000000;i++)
		{
			inv[i]=(mod-mod/i)*inv[mod%i]%mod;
			fac[i]=fac[i-1]*i%mod;
		}
		
		inv[0]=1;
		for(int i=1;i<=1000000;i++)
		{
			inv[i]=inv[i]*inv[i-1]%mod;
		}
		
		per[0]=1;
		for(int i=1;i<=1000000;i++)
		{
			per[i]=(per[i-1]*i%mod+(i%2==1?-1:1))%mod;
		}
	}
	
	long long C(int a,int b)
	{
		return fac[a]*inv[a-b]%mod*inv[b]%mod;
	}
	
	void work()
	{
		init();
		scanf("%d",&t);
		while(t--)
		{
			scanf("%d%d",&n,&m);
			printf("%lld\n",C(n,m)*per[n-m]%mod);
		}
	}
	
}

int main()
{
	zzc::work();
	return 0;
 }