Luogu 2822[NOIP2016] 组合数问题

题解

乱搞就能过了。

首先我们考虑如何快速判断C(i, j ) | k 是否成立。

由于$k$非常小, 所以可以对$k$分解质因数, 接着预处理出前N个数的阶乘的因数中 $p_i$ 的个数, 然后就可以$O(1)$判断C(i,j)| k 

然后用mk[i][j] 记录 C(i, j) | k , 并将它转化为二位前缀和, 每次查询只需要输出mk[ n ][ m ]即可

预处理时间复杂度:$O(NlnN + NM)$

每次查询$O(1)$

代码

 1 #include<cstring>
 2 #include<cmath>
 3 #include<cstdio>
 4 #include<algorithm>
 5 #define rd read()
 6 
 7 const int N = 2e3 + 5;
 8 
 9 int T, k, n, m;
10 int pri[5], cnt[5], tot;
11 int num[N][5], mk[N][N];
12 
13 int read() {
14     int X = 0, p = 1; char c = getchar();
15     for(; c > '9' || c < '0'; c = getchar()) if(c == '-') p = -1;
16     for(; c >= '0' && c <= '9'; c = getchar()) X = X * 10 + c - '0';
17     return X * p;
18 }
19 
20 int fpow(int a, int b) {
21     int re = 1;
22     for(; b; b >>= 1, a *= a) if(b & 1) re *= a;
23     return re;
24 }
25 
26 int jud(int x, int y) {
27     for(int i = 1; i <= tot; ++i) {
28         int re = 0;
29         re += num[x][i];
30         re -= num[y][i];
31         re -= num[x - y][i];
32         if(re < cnt[i]) return 0;
33     }
34     return 1;
35 }
36 
37 void init() {
38     int t = k;
39     for(int i = 2; i <= k; ++i) if(t % i == 0) {
40         pri[++tot] = i;
41         while(t % i == 0) cnt[tot]++, t /= i;
42     }
43     for(int j = 1; j <= tot; ++j) 
44         for(int l = 1; ; ++l) {
45             int p = fpow(pri[j], l);
46             if(p >= N) break;
47             for(int i = 1; i < N; ++i) num[i][j] += i / p;
48         }
49     for(int i = 1; i < N; ++i)
50         for(int j = 1; j <= i; ++j) if(jud(i, j)) mk[i][j] = 1;
51     for(int i = 1; i < N; ++i)
52         for(int j = 1; j < N; ++j) mk[i][j] += mk[i - 1][j];
53     for(int i = 1; i < N; ++i)
54         for(int j = 1; j < N; ++j) mk[i][j] += mk[i][j - 1];
55 }
56 
57 int main()
58 {
59     T = rd; k = rd;
60     init();
61     for(; T; T--) {
62         n = rd; m = rd;
63         printf("%d
", mk[n][m]);
64     }
65 }
View Code
原文地址:https://www.cnblogs.com/cychester/p/9528170.html