(组合数学)连续n个物品,总共选了m个,选中的物品连续分布最长为k个的方案数。

题目:

连续n个物品,总共选了m个,选中的物品连续分布最长为k个的方案数

算法:

组合计数+容斥

分析:

在计算连续分布最长为k个的方案数之前,我们先考虑n个物品,总共选m个,选中的物品连续分布最长大于等于k的方案数。

我们记F[k]为个物品,总共选m个,选中的物品连续分布最长大于等于k的方案数,显然题目要求的方案数就是F[k]-F[k+1]。

接下来求F[k],总共有 n-m 个物品没有被选上,我们把这 n-m 个物品看作隔板,用插空法插入选中的物品,因为至少要选一段连续的大于等于k个,我们先枚举连续大于等于k个的选了多少段,再计算连续选的大于等于k个在哪几个空,这样计算显然会算重复,因此需要容斥。

我们计i为连续大于等于k个的段数,n-m 个隔板隔出了 n-m+1 个空,连续大于等于k个的段数插在哪些空的方案数为C(n-m+1, i),剩下的选的物品可以任意放,方案数为C(n-i*k, n-m),即在除连续大于等于k个物品之外剩下的物品 n-i*k 个中,选出 n-m 个当隔板。

F[k] = \sum_{i=1}^{i*k\leqslant m}(-1)^{i+1}*C(n-m+1,i)*C(n-i*k,n-m)

最后答案即为 F[k]-F[k+1]

代码:
const int N = 1e6+10;
const int MOD = 998244353;
int fac[N],ifac[N];
int qmi(int a,int b,int p){
	int res=1;
	while(b){
		if(b&1)res=res*a%p;
		a=a*a%p;
		b>>=1;
	}
	return res;
}
int get_inv(int x){
	return qmi(x,MOD-2,MOD);
}
void init(){
	fac[0]=1;
	for(int i=1;i<N;i++)fac[i]=fac[i-1]*i%MOD;
	ifac[N-1]=1*get_inv(fac[N-1]);
	for(int i=N-1;i>0;i--)ifac[i-1]=ifac[i]*i%MOD;
}
int C(int n, int m){
	if(m>n)return 0;
	if(m==0)return 1;
	return (fac[n]*ifac[n-m]%MOD)*ifac[m]%MOD;
}
int F(int n,int m,int k){
	int res=0,st=1;
	for(int i=1;i*k<=m&&i<=n-m+1;i++){
        if(st)res=(res+C(n-m+1,i)*C(n-i*k,n-m)%MOD)%MOD;
        else res=(res-C(n-m+1,i)*C(n-i*k,n-m)%MOD+MOD)%MOD;
        st^=1;
	}
	return res;
}
void solve(){
	int n,m,k;
	cin>>n>>m>>k;
	init();
	cout<<(F(n,m,k)-F(n,m,k+1)+MOD)%MOD<<endl;
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值