星星之火

[牛客挑战赛 30D] 小A的昆特牌 解题报告 (组合数学)

interlinkage:

https://ac.nowcoder.com/acm/contest/375/D

description:

solution:

  • 我们枚举步兵的数量$x$,还剩下$S-x$张牌。$x$张步兵要分成$n$份,$S-x$剩下的要分成$m+1$份,其中第$m+1$份的含义是不锻造,注意可以为空
  • $ans=\sum_{x=l}^{r}\dbinom{x+n-1}{n-1}\dbinom{S-x+m}{m}$
  • 但是直接这样算的话要么爆时间,要么爆空间
  • 发现答案的式子其实相当于从$(0,0)$走到$(S,n+m)$必须经过线段$(l,n)->(r,n)$的方案数
  • 可能有人会疑惑为什么是从$(0,0)$走到$(S,n+m)$,感觉像是走到$(S,n+m+1)$啊。但是仔细观察会发现,因为我们枚举的是与线段$(l,n)->(r,n)$的交点,也就是说当走到$y=n$的时候交点就已经固定了,就不能再向右走了。因此从$(0,0)$到$(x,n)$相当于把$x$个横步插入到$n$个部分中。从$(x,n)$到$(S,n+m)$相当于把$S-x$的横步插入到$m+1$个部分中,因为这个时候走到$y=n+m$的时候还可以向右走
  • 该方案数=第$l$步向右走时走到纵坐标$(0,n-1)$的方案数-第$r+1$步向右走时走到纵坐标$(0,n-1)$的方案数
  • 走到第$p$步向右走时走到纵坐标$(0,n-1)$的方案数为$\sum_{i=0}^{n-1}\dbinom{p+i-1}{p-1}\dbinom{S-p+n+m-i}{S-p}$
  • 这样就比较好算了
#include<bits/stdc++.h>
using namespace std;

const int N=2e7+5,P=998244353;
inline int add(int x,int y){return x+y>=P?x+y-P:x+y;}
inline int dec(int x,int y){return x-y<0?x-y+P:x-y;}
inline int mul(int x,int y){return 1ll*x*y-1ll*x*y/P*P;}
int inv[N],f[N],g[N];
int n,m,s,l,r;
int calc(int p)
{
    if(p>s)return 0;
    int res=0;
    f[0]=g[0]=1;
    for (int i=1;i<=n+m;i++)
    {
        g[i]=1ll*g[i-1]*(p+i-1)%P*inv[i]%P,
        f[i]=1ll*f[i-1]*(s-p+i)%P*inv[i]%P;
    }
    for (int i=0;i<n;i++) res=add(res,mul(f[n+m-i],g[i]));
    return res;
}
int main()
{
    scanf("%d%d%d%d%d",&n,&m,&s,&l,&r);
    inv[0]=inv[1]=1;
    for (int i=2;i<N;i++) inv[i]=1ll*(P-P/i)*inv[P%i]%P;
    printf("%d\n",dec(calc(l),calc(r+1)));
    return 0;
}

 

posted @ 2019-04-12 08:54  星星之火OIer  阅读(87)  评论(0编辑  收藏