[SDOI2016]排列计数

洛咕

题意:求有多少种 \(1\)\(n\) 的排列 \(a\),满足序列恰好有 \(m\) 个位置 \(i\),使得 \(a_i = i\)。答案对 \(10^9 + 7\) 取模。

分析:首先写出最基本的递推式,设\(f[i][j]\)表示\(1\)\(i\) 的排列,有 \(j\) 个位置满足条件 的方案数,则\(f[i][j]=f[i-1][j-1]+f[i-1][j]*(i-1-j)+f[i-1][j+1]*(j+1)\),然后就可以打表找规律了。规律公式就是\(T(n-m, 0)*C(n, m)\),第一项实际上就是\(n-k\)个数的错排,第二项就是组合数。

错排数可以用递推公式\(O(n)\)预处理,\(D(n)=(n-1)*(D(n-1)+D(n-2))\)。然后还要预处理阶乘以及阶乘在\(mod\)意义下的乘法逆元。每次询问\(O(1)\)回答。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
inline int read(){
    int x=0,o=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')o=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*o;
}
const int N=1e6+5;
const int mod=1e9+7; 
int f[N],jc[N],inv_jc[N];
int quickpow(int a,int b){
	int cnt=1;
    while(b){
    	if(b&1)cnt=(1ll*cnt*a)%mod;
    	a=(1ll*a*a)%mod;
    	b=b>>1;
    }
    return cnt;
}
int main(){
	//T(n-k, 0)*C(n, k)
	f[0]=1;f[1]=0;f[2]=1;
	for(int i=3;i<=1000000;++i){
		f[i]=(1ll*((f[i-1]+f[i-2])%mod)*(i-1))%mod;
	}
	
	jc[0]=1;
	for(int i=1;i<=N;i++) 
    	jc[i]=(1ll*jc[i-1]*i)%mod; 
	inv_jc[N]=quickpow(jc[N],mod-2);
	for(int i=N-1;i>=0;i--) 
   		inv_jc[i]=(1ll*inv_jc[i+1]*(i+1))%mod;
   		
	int T=read();
	while(T--){
		int n=read(),m=read();
		int ans=1ll*f[n-m]*jc[n]%mod*inv_jc[m]%mod*inv_jc[n-m]%mod;
		cout<<ans<<endl;
	}
	return 0;
}

posted on 2023-02-24 15:33  PPXppx  阅读(12)  评论(0编辑  收藏  举报