题目链接
NOIP2016 提高组 D2T1
组合数 ( 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\mid\binom{i}{j} k∣(ji)。
第一行有两个整数 t , k t,k t,k,其中 t t t 代表该测试点总共有多少组测试数据, k k k 的意义见问题描述。
接下来 t t t 行每行两个整数 n , m n,m n,m,其中 n , m n,m n,m 的意义见问题描述。
共 t t t 行,每行一个整数代表所有的 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\mid\binom{i}{j} k∣(ji)。
1 2
3 3
1
2 5
4 5
6 7
0
7
【样例1说明】
在所有可能的情况中,只有 ( 2 1 ) = 2 \binom{2}{1} = 2 (12)=2 一种情况是 2 2 2 的倍数。
【子任务】
题目分析:这道题暴力算的话会超时,方法描述:对于每组测试数据
(n,m)遍历所有可能的 (i,j),计算组合数 (i,j),并判断是否能被 k 整除。每次查询都需要重新遍历和计算。
时间复杂度:
对于每组测试数据,需要遍历 O(n×m)对于(i,j)。如果有 t 组测试数据,总时间复杂度为:O
(t×n×m)对于 题目数据超时了,查询部分时间复杂度过高。一个很容易想到的优化便是:在预处理时,同时预处理出每个 n , m 对应的答案,将查询部分优化到 O ( 1 ) ,用到了二维前缀和
#include
using namespace std;
typedef long long ll;
const int N = 2010;
int t,n,k,m;
ll a[N][N],c[N][N];
void Init()
{
c[0][0] = 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] + c[i-1][j-1]) % k;
a[i][j] = a[i-1][j] + a[i][j-1] - a[i-1][j-1];
if(!c[i][j]) a[i][j] ++;
}
a[i][i + 1] = a[i][i];
}
}
ll read()
{
ll s=0,f=1;
char ch=getchar();
while (ch<'0'||ch>'9')
{
if (ch=='-') f=-1;
ch=getchar();
}
while (ch>='0'&&ch<='9')
{
s=s*10+ch-'0';
ch=getchar();
}
return s*f;
}
void sol()
{
n = read(),m = read();
if(m > n) cout<