P2822 组合数问题
知识点: 数论,思路题,二维前缀和
原题面
题目要求:
先给定 \(k\) ,
多次询问,每次询问给定 \(n,m\)
求 有多少对 \((i,j)\) 满足 \(\large C_i^j\) 是 \(k\) 的倍数
\(0\le i\le n , 0\le j\le min(i,m)\)
分析题意:
发现 \(n,m\le 2000\) , 但是查询次数 \(t\le 1e4\) ;
要求一个 较低的查询复杂度.
考虑预处理.
根据 杨辉三角 和组合数的性质:
杨辉三角: 第 \(i\) 行第 \(j\) 列 = 第 \(i-1\) 行第 \(j\) 列 + 第 \(i-1\) 行第 \(j-1\) 列
\(\large = C_{i-1}^{j-1} =\) 从 \(i-1\) 个物品中 选择 \(j-1\) 个物品的方案数
那么可以预处理出 给定范围内所有的组合数
由于要求 满足\(\large C_i^j\ \%k=0\) 的对数,
可以在杨辉三角递推的过程中,同时进行取模运算
并记录二维前缀和,
即 到达第 \(\large C_i^j\) 时 , \(\large C_{i1}^{j1} \%k=0\) 的对数.
最后对于每次询问,
\(O(1)\) 查询二维前缀和答案 即可.
#include<cstdio>
#include<ctype.h>
#define int long long
//=============================================================
const int MARX = 2010;
int t,k,n,m;
int a[MARX][MARX];//存 C(i,j) % k
int ans[MARX][MARX];//存 到C(i,j) 有多少对满足条件的答案
//=============================================================
inline int read()
{
int s=1, w=0; char ch=getchar();
for(; !isdigit(ch);ch=getchar()) if(ch=='-') s =-1;
for(; isdigit(ch);ch=getchar()) w = w*10+ch-'0';
return s*w;
}
//=============================================================
signed main()
{
t=read(),k=read();
a[0][0]=a[1][0]=a[1][1]=1;//初始化杨辉三角
for(int i=2;i<=2000;i++)
{
a[i][0]=1;//首项
for(int j=1;j<=i;j++)//递推
{
a[i][j]=(a[i-1][j-1] + a[i-1][j])%k;//递推,求得C(i,j)
ans[i][j]=ans[i-1][j]+ans[i][j-1]-ans[i-1][j-1];//记录前缀答案
if(!a[i][j])ans[i][j]++;//C(i,j) 合法
}
ans[i][i+1]=ans[i][i];//记录前缀答案
}
while(t--)//O(1)回答询问
{
n=read(),m=read();
if(m>n) printf("%lld\n",ans[n][n]);
else printf("%lld\n",ans[n][m]);
}
}
作者@Luckyblock,转载请声明出处。

浙公网安备 33010602011771号