序列统计 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;
}
总结
- 隔板法适用于解决n的组成问题.
- 组合数可以用递推公式压缩