【牛客】寒假训练营1 F-鸡数题 题解

传送门:鸡数题
标签:组合数学

题目大意

问有多少个长度为m的正数升序序列满足以下条件:1、序列中所有数按位相或的结果为2n-1。2、序列中任意两个数按位相与的结果为0。
输入:两个正整数n,m,含义如题中所述。
输出:符合条件的序列数量,答案对1e9+7取模。

算法分析

  • 题目一看就具有迷惑性。首先序列中所有数相或等于2n-1说明我们可以把序列中每个数都用二进制表示,且从第0位~第n-1位的1都至少出现在一个数中。再看第二个条件,不难发现序列中任意两个数不可能有某一位同时为1。那么问题就转化为了将n个1分配给m个数,且不能有数为0。又因为要求序列升序,所以可以看作将n个不同的小球分配给m个相同的盒子。看到这里你肯定会想到高中学过的组合数学——隔板法。但仔细一看会发现这题不简单,因为这里的n个小球是无序的,而隔板法要求待分配的小球是有序的。
  • 我们先引入第二类斯特林数的概念,它表示的是在集合不能为空的条件下把 n 个不同元素划分到 m 个集合的方案数,这里记为Stl(n,m)。显然当n=m时只有一种分划方案,当m=0时没有方案,这就是边界条件。再考虑普遍情况,我们假设第一个数单独占据一个集合,那么问题就转化为剩下n-1个数划分到m-1个集合中,即Stl(n-1,m-1)。再假设第一个数所在的集合中还有其它元素,那么我们只要先算出剩下n-1个数分划到m个集合中的方案数,然后将第一个数插入任意一个集合中即可,也就是Stl(n-1,m)*m。
  • 这样一来就得到通项公式:Stl(n,m)=Stl(n-1,m-1)+Stl(n-1,m)*m,也知道了边界条件。但这样还不够,我们不难发现递归的做法的时间复杂度约为O(2n),可以说是非常的糟糕。这里就要使用容斥的思想以及组合数学来化简公式了。将递归层层展开后我们会发现有大量的重复计算,再用组合数表示后就能将复杂度降低为O(mlogn),最后公式如下。复杂度主要来自于快速幂,因为组合数可以线性求得(虽然我代码还是用费马小定理),这里就不过多赘述了。
    请添加图片描述

代码实现

#include <iostream>
using namespace std;
long long inv[100001]={0LL,1LL};
long long pow3[100001]={1LL},fact[100001]={1LL},ift[100001]={1LL};
const int mod=1e9+7;
long long pow_mod(long long a,long long b){
	long long x=1LL;
	a%=mod;
	while(b){
		if(b&1LL)x=x*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return x;
}
long long zuhe(int m,int n){
	if(m>n)return 0LL;
	return fact[n]*(pow_mod(fact[n-m],mod-2)*pow_mod(fact[m],mod-2)%mod)%mod;
}
int main(){
	long long i,n,m,ans=0,flag=1;
	for(i=2;i<=1e5;i++)
		inv[i]=mod-mod/i*inv[mod%i]%mod;
	for(i=1;i<=1e5;i++){
		pow3[i]=pow3[i-1]*3%mod;
		fact[i]=fact[i-1]*i%mod;
		ift[i]=ift[i-1]*inv[i]%mod;
	}
	cin>>n>>m;
	for(i=0;i<=m;i++){
		ans=(ans+flag*zuhe(i,m)*pow_mod(m-i,n)%mod+mod)%mod;
		flag*=-1;
	}
	cout<<ans*ift[m]%mod;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值