众所周知(C_n^m=frac{n!}{m!(m-n)!}),特别地,定义 0!=1
例题在这里:传送门
level 0
一开始的思路必然是挨个算组合数,但是那样必然会超时
level 1
组合数递推法:
(C_n^m=C_{n}^{n-m})
故:
(C_n^m=C_{n-1}^{m-1}+C_{n-1}^{m})
所以可以看出组合数其实是个杨辉三角
(C_0^0=C_0^i=C_1^1=1)
所以用杨辉三角打表比刚刚那个直接求快很多
void build()
{
c[0][0]=1;
c[1][0]=c[1][1]=1;//初始化
for(int i=2;i<=2000;i++)
{
c[i][0]=1;
for(int j=1;j<=2000;j++
c[i][j]=c[i-1][j-1]+c[i-1][j];//递推公式。
}
}
level 2
取模大法好!!
({C_n^m}mod k)=({C_{n-1}^{m-1}mod k}+{C_{n-1}^{m}mod k})
void build()
{
c[0][0]=1;
c[1][0]=c[1][1]=1;
for(int i=2;i<=2000;i++)
{
c[i][0]=1;
for(int j=1;j<=2000;j++)
c[i][j]=(c[i-1][j-1]%k+c[i-1][j]%k)%k;//重中之重,绝不能盲目地模。
}
}
level 3
dl“瞎搞”出的玄学优化(呜呜呜我什么时候能像大佬一样厉害)
因为取模运算比四则运算都耗时,所以大佬想尽一切办法减少取模
所以在一开始膜的时候就判断一下
void build()
{
c[0][0]=1;
c[1][0]=c[1][1]=1;
for(int i=2;i<=2000;i++)
{
c[i][0]=1;
for(int j=1;j<=2000;j++)
{
c[i][j]=(c[i-1][j-1]+c[i-1][j])%k;
if(c[i][j]%k==0)s[i][j]=1;//先记一下,如果已经满足条件,就标记一下,省去了更多的计算。
}
}
}
void solve()
{
t=read(),k=read();
build();
while(t--)
{
ans=0;
n=read(),m=read();
for(int i=0;i<=n;i++)
for(int j=0;j<=my_min(i,m);j++)
ans+=s[i][j];
printf("%lld
",ans);
}
}
level 4
谁能想到用前缀和计算组合数呢
二维的前缀和记住:上加左减左上加自己
即:ans[i][j]=ans[i][j-1]+ans[i-1][j]-ans[i-1][j-1]
这一句是处理边界要加上的:ans[i][i+1]=ans[i][i]
这个题是一个裸的前缀和!!所以你求出ans[i][j]直接就是答案了
inline void build()
{
c[0][0]=1;
c[1][0]=c[1][1]=1;
for(int i=2;i<=2000;i++)
{
c[i][0]=1;
for(int j=1;j<=i;j++)
{
c[i][j]=(c[i-1][j-1]+c[i-1][j])%k;
ans[i][j]=ans[i-1][j]+ans[i][j-1]-ans[i-1][j-1];//前缀和(没加自己是因为自己本来就是0)
if(!c[i][j])ans[i][j]++;//如果满足结论,计数加一
}
ans[i][i+1]=ans[i][i];//弥补杨辉三角
}
}
inline void solve()
{
t=read(),k=read();
build();
while(t--)
{
n=read(),m=read();
if(m>n)printf("%lld
",ans[n][n]);//如果m>n,ans只会达到n,只需输出ans[n,n]就可以了。
else printf("%lld
",ans[n][m]);
}
}
最后!
感谢大佬的博客!!!