2018.10.25 bzoj4517: [Sdoi2016]排列计数(组合数学)

传送门
组合数学简单题。


A n s = ( n m ) ∗ 1 Ans=\binom {n} {m}*1 Ans=(mn)1~ ( n − m ) (n-m) (nm)的错排数。
前面的直接线性筛逆元求。
后面的错排数递推式本蒟蒻竟然推出来了。
首先说说为什么 A n s = ( n m ) ∗ 1 Ans=\binom {n} {m}*1 Ans=(mn)1~ n n n- m m m的错排数。
考虑首先选出 m m m个排列正确的数有 ( n m ) \binom {n} {m} (mn)种选法。
然后剩下的 n − m n-m nm个数因为有严格的大小关系相当于只需要保证每个数与其下标不相同。
那么我们把这 n − m n-m nm个数提出来。
它们的错排数跟 1 1 1~ n n n- m m m的错排数是相同的。
因此就是是这样了。
所以错排数怎么推呢?
假设已经求出了 1 , 1 1,1 1,1~ 2 , 1 2,1 2,1 ~ 3 3 3 1 1 1 ~ n n n- 1 1 1的错排数,要求 1 1 1~ n n n的错排数。
我们设 1 1 1~ i i i的错排数为 f [ i ] f[i] f[i]
考虑现在在某个排列 1 1 1~ i − 1 i-1 i1中加入 i i i ( i ≥ 2 ) (i \geq 2) (i2)
那么有两种情况。

  1. 已有的排列中排列正确的数个数为0,那么只用从原排列中随便选个数放到第 i i i个位置,然后拿 i i i去填空就行了,方案数为 ( i − 1 ) ∗ f [ i − 1 ] (i-1)*f[i-1] (i1)f[i1]
  2. 已有的排列中排列正确的数个数为1,那么把这个数挪到第 i i i个位置,然后用 i i i去填空就行了,由于 i − 1 i-1 i1个数都有可能成为那个排列正确的数,而且对于剩下的 i − 2 i-2 i2个数都是错排的,因此方案数为 ( i − 1 ) ∗ f [ i − 2 ] (i-1)*f[i-2] (i1)f[i2]
    => f [ i ] = ( i − 1 ) ∗ ( f [ i − 1 ] + f [ i − 2 ] ) f[i]=(i-1)*(f[i-1]+f[i-2]) f[i]=(i1)(f[i1]+f[i2])

代码:

#include<bits/stdc++.h>
using namespace std;
inline int read(){
	int ans=0;
	char ch=getchar();
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))ans=(ans<<3)+(ans<<1)+(ch^48),ch=getchar();
	return ans;
}
const int mod=1e9+7,N=1e6+5;
int T,n,m,f[N],ifac[N],fac[N];
int main(){
	T=read();
	f[0]=1,f[1]=0,fac[0]=fac[1]=ifac[1]=ifac[0]=1;
	for(int i=2;i<=N-5;++i)fac[i]=1ll*fac[i-1]*i%mod,ifac[i]=1ll*(mod-mod/i)*ifac[mod%i]%mod,f[i]=1ll*(f[i-1]+f[i-2])*(i-1)%mod;
	for(int i=2;i<=N-5;++i)ifac[i]=1ll*ifac[i]*ifac[i-1]%mod;
	while(T--)n=read(),m=read(),printf("%d\n",1ll*fac[n]*ifac[m]%mod*ifac[n-m]%mod*f[n-m]%mod);
	return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值