洛谷P2822 [NOIP2016 提高组] 组合数问题 题解
题目链接:P2822 [NOIP2016 提高组] 组合数问题
题意:
组合数 ( n m ) \binom{n}{m} (mn) 表示的是从 n n n 个物品中选出 m m m 个物品的方案数。举个例子,从 ( 1 , 2 , 3 ) (1,2,3) (1,2,3) 三个物品中选择两个物品可以有 ( 1 , 2 ) , ( 1 , 3 ) , ( 2 , 3 ) (1,2),(1,3),(2,3) (1,2),(1,3),(2,3) 这三种选择方法。根据组合数的定义,我们可以给出计算组合数 ( n m ) \binom{n}{m} (mn) 的一般公式:
( n m ) = n ! m ! ( n − m ) ! \binom{n}{m}=\frac{n!}{m!(n-m)!} (mn)=m!(n−m)!n!
其中 n ! = 1 × 2 × ⋯ × n n!=1\times2\times\cdots\times n n!=1×2×⋯×n;特别地,定义 0 ! = 1 0!=1 0!=1。小葱想知道如果给定 n , m n,m n,m 和 k k k,对于所有的 0 ≤ i ≤ n , 0 ≤ j ≤ min ( i , m ) 0\leq i\leq n,0\leq j\leq \min \left ( i, m \right ) 0≤i≤n,0≤j≤min(i,m) 有多少对 ( i , j ) (i,j) (i,j) 满足 k ∣ ( i j ) k|\binom{i}{j} k∣(ji)。
- 对于全部的测试点,保证 0 ≤ n , m ≤ 2 × 1 0 3 0 \leq n, m \leq 2 \times 10^3 0≤n,m≤2×103, 1 ≤ t ≤ 1 0 4 1 \leq t \leq 10^4 1≤t≤104。
显然杨辉三角的那个预处理
询问可以用二维前缀和来搞
这里的前缀和有个有趣的地方
s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+(c[i][j]==0)
这里在获取 s i − 1 , j s_{i-1,j} si−1,j 的时候,如果 j = i j=i j=i ,那 s i − 1 , j s_{i-1,j} si−1,j 就没有算,会导致出错
解决的方法很简单,直接在每次计算时把 s i , i + 1 ← s i , i s_{i,i+1}\leftarrow s_{i,i} si,i+1←si,i 就好了
这样的话没有影响前缀和数组的意义,蛮好
代码:
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <iomanip>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
#define N (int)(2e3+15)
#define _ 2000
int k,c[N][N],s[N][N];
void init()
{
c[1][1]=1;
for(int i=0; i<=_; i++) c[i][0]=1;
for(int i=2; i<=_; i++)
for(int j=1; j<=i; j++)
c[i][j]=(c[i-1][j]+c[i-1][j-1])%k;
for(int i=2; i<=_; i++)
{
for(int j=1; j<=i; j++)
s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+(c[i][j]==0);
s[i][i+1]=s[i][i];
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
// freopen("check.in","r",stdin);
// freopen("check.out","w",stdout);
int Q,n,m;
cin >> Q >> k;init();
while(Q--)
{
cin >> n >> m;
if(m>n)m=n;
cout << s[n][m] << '\n';
}
return 0;
}
转载请说明出处