序列统计 LibreOJ - 10235

原题链接
考察:组合计数
思路:
  有两种推到最后\(C_{r+l+k}^k\)的方法.主要思想就是构造严格单调递增的序列.
方法一:

\[L<=a_1<=a_2<=a_3<=...<=a_k<=R \]

  如果直接求的话,需要讨论用几个数填满k个位置,这就是相当于隔板法.但是k范围太大,难以预处理此方法不可行.
  很好的思路就是构造互不相同的序列,这样就不用考虑相等.需要构造:

\[b_i = a_i+i (L+1<=b_i<=R+k) \]

  这样最后就是在\(L+1,R+k\)之间选互不相同的k个数.即\(C_{r+l+k}^k\)

  
方法二:
  大同小异.因为:

\[L<=a_1<=a_2<=a_3<=...<=a_k<=R \]

  考虑隔板法,用LR之间的数填满k个位置.这里至少为L不好控制,所以将$a_i$映射为$0$\(R-L\),这就可以用隔板法解决,但是隔板法要求至少为1,所以令

\[a_i - a_{i-1} +1 =x_i \]

\[x_1+x_2+...x_i<=R-L+k \]

那么就是\(R-l+k\)选k个挡板(为k是因为和是<=\(R-l+k\)),最后一个挡板定和的范围.

  最后就是推导\(C_{r+l+k}^k\),需要用到组合数的递推式,反正我是想不到()

Code

#include <iostream>
#include <cstring>
using namespace std;
typedef long long LL;
const int M = 1e6+3;
int n,l,r;
LL qsm(LL a,int k,int m)
{
    LL res = 1;
    while(k)
    {
        if(k&1) res = res*a%m;
        a = a*a%m;
        k>>=1;
    }
    return res;
}
int C(int a,int b) 
{
    LL up = 1,down = 1;
    for(int i=a,j=1;j<=b;j++,i--)
    {
        up = up*i%M;
        down = down*j%M;
    }
    return up*qsm(down,M-2,M)%M;
}
int lucas(int a,int b)
{
    if(a<M&&b<M) return C(a,b);
    return (LL)lucas(a/M,b/M)*lucas(a%M,b%M)%M;
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d%d",&n,&l,&r);
        LL ans = ((LL)lucas(r-l+1+n,r-l+1)-1)%M;;
        printf("%lld\n",(ans+M)%M);
    }
    return 0;
}

总结

  1. 隔板法适用于解决n的组成问题.
  2. 组合数可以用递推公式压缩
posted @ 2021-06-11 10:56  acmloser  阅读(40)  评论(0编辑  收藏  举报