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;
 } 
posted @ 2020-09-16 22:30  youth518  阅读(114)  评论(0编辑  收藏  举报