把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【牛客挑战赛30D】小A的昆特牌(组合问题抽象到二维平面)

点此看题面

大致题意:\(S\)张无编号的牌,可以将任意张牌锻造成\(n\)种步兵或\(m\)种弩兵中的一种,求最后步兵数量大于等于\(l\)小于等于\(r\)的方案数。

暴力式子

首先我们来考虑暴力式子。

假设我们确定了要选\(x\)个步兵数量,然后要求出此时的方案数。

则我们就要使用隔板法

仔细思考,其实我们就相当于要求出\(x\)个步兵分成\(n\)\(S-x\)个步兵分成\(m+1\)的方案数的乘积。(其中\(m+1\)组指的是\(m\)种弩兵以及不锻造这\(m+1\)种情况)

而这两个要求的东西本质上是一样的。

以把\(x\)个步兵分成\(n\)组为例,考虑到这是无编号的,因此我们完全可以假设每个步兵被分到的组号是递增的。

那也就是说,我们要求把一个长度为\(x\)的序列分割成\(n\)部分(可以为空)的方案数。

而分割成\(n\)部分,就相当于加入了\(n-1\)块隔板。

如果把隔板也看做序列的一部分,则序列总长度就变成了\(x+n-1\),而分割就相当于要在这个序列中选出\(n-1\)个位置。

因此方案数就是:

\[C_{x+n-1}^{n-1} \]

同理,把\(S-x\)个步兵分成\(m+1\)组的方案数就是:

\[C_{s-x+m}^m \]

于是它们的乘积就是:

\[C_{x+n-1}^{n-1}*C_{s-x+m}^m \]

而最终答案就是:

\[\sum_{x=l}^rC_{x+n-1}^{n-1}*C_{s-x+m}^m \]

抽象问题到二维平面

我们可以发现,这其实就相当于从\((0,0)\)走到\((S,n+m)\),且必须经过点\((l,n)\)下方,不能经过点\((r+1,n)\)下方的方案数。

这其实就相当于用经过点\((l,n)\)下方的方案数减去经过点\((r+1,n)\)下方的方案数。

应该还是比较简单的。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 10000000
#define X 998244353
#define max(x,y) ((x)>(y)?(x):(y))
#define Inc(x,y) ((x+=(y))>=X&&(x-=X))
using namespace std;
int n,m,s,l,r,Inv[(N<<1)+5],p1[N+5],p2[N+5];
I int C(CI x,CI y)//求组合数
{
	if(x<y||x<0) return 0;RI i,res=1;//判断越界
	for(i=1;i<=y;++i) res=1LL*res*(x-i+1)%X*Inv[i]%X;//统计答案
	return res;//返回答案
}
I int Calc(CI x)//计算经过点(x,n)下方的方案数
{
	RI i,res=0;for(p1[0]=i=1;i^n;++i) p1[i]=1LL*p1[i-1]*(x-1+i)%X*Inv[i]%X;//计算p1
	for(p2[n-1]=C(s-x+m+1,m+1),i=n-2;~i;--i) p2[i]=1LL*p2[i+1]*(s-x+n+m-i)%X*Inv[n+m-i]%X;//计算p2
	for(i=0;i^n;++i) Inc(res,1LL*p1[i]*p2[i]%X);return res;//统计答案
}
int main()
{
	RI i,lim;scanf("%d%d%d%d%d",&n,&m,&s,&l,&r);
	for(Inv[0]=Inv[1]=1,i=2,lim=max(n,m)<<1;i<=lim;++i) Inv[i]=1LL*(X-X/i)*Inv[X%i]%X;//线性求逆元
	return printf("%d",(Calc(l)-Calc(r+1)+X)%X),0;//输出答案
}
posted @ 2019-03-25 23:39  TheLostWeak  阅读(204)  评论(0编辑  收藏  举报