2018.09.18【BZOJ4517】【SDOI2016】排列计数(组合数学)(错排问题)

版权声明:转载请声明出处,谢谢配合。 https://blog.csdn.net/zxyoi_dreamer/article/details/82764647

Description

求有多少种长度为 n 的序列 A,满足以下条件:
1 ~ n 这 n 个数在序列中各出现了一次
若第 i 个数 A[i] 的值为 i,则称 i 是稳定的。序列恰好有 m 个数是稳定的
满足条件的序列可能很多,序列数对 10^9+7 取模。

Input

第一行一个数 T,表示有 T 组数据。
接下来 T 行,每行两个整数 n、m。
T=500000,n≤1000000,m≤1000000

Output

输出 T 行,每行一个数,表示求出的序列数

Sample Input

5
1 0
1 1
5 2
100 50
10000 5000

Sample Output

0
1
20
578028887
60695423


解析:

感觉好简单的签到题。

一看题目就是一个组合数学,并且要求 O ( 1 ) O(1) 完成询问。
好吧我还以为很难。。。然后5分钟写完代码。。。

由于数据范围很大,显然需要我们预处理阶乘以及阶乘逆元,那就递推一下吧。

然后我们再看,要求恰好 m m 个数是在正确的位置上。那么我们选取这 m m 个数的方案数就是 ( m n ) (^n_m) 。那么我们再处理一个错排方案数。递推式如下 d 0 = d 1 = 0 , d 2 = 1 d_0=d_1=0,d_2=1 d n = ( n 1 ) ( d n 1 + d n 2 ) d_n=(n-1)*(d_{n-1}+d_{n-2})

网上关于错排问题的详解已经很多了,这里就不具体阐述。

然后就是细节。

显然阶乘求组合数的细节想必不用我多说,主要这里的错排。

m < n m<n 时候,一切都好说。

但是错排问题中 d 0 = 0 d_0=0 ,但是这里当 n = = m n==m 的时候显然有一种排列方式满足题意。而 d 0 d_0 在递推中又不会用到,那么就直接初始化为 1 1
然后发现错排的递推能够和其他递推一起写了。


代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc getchar
#define pc putchar
#define cs const

cs ll mod=1000000007;

inline
ll getint(){
	re ll num=0;
	re char c;
	while(!isdigit(c=gc()));
	while(isdigit(c))num=(num<<1)+(num<<3)+(c^48),c=gc();
	return num;
}

inline
void outint(ll a){
	static char ch[23];
	if(a==0)pc('0');
	while(a)ch[++ch[0]]=(a-a/10*10)^48,a/=10;
	while(ch[0])pc(ch[ch[0]--]);
}

ll d[1000001]={1,0},fac[1000001]={1,1},inv[1000001]={1,1},ifac[1000001]={1,1};

inline
ll c(int n,int m){
	if(m>n)return 0; 
	return fac[n]*ifac[m]%mod*ifac[n-m]%mod;
}

int T;
int main(){
	
	for(int re i=2;i<=1000000;++i)
	fac[i]=fac[i-1]*i%mod,
	inv[i]=(mod-mod/i)*inv[mod%i]%mod,
	ifac[i]=ifac[i-1]*inv[i]%mod,
	d[i]=(d[i-1]+d[i-2])%mod*(i-1)%mod;
	
	T=getint();
	while(T--){
		int n=getint(),m=getint();
		outint(d[n-m]*c(n,m)%mod);
		pc('\n');
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/zxyoi_dreamer/article/details/82764647
今日推荐