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\n", mk[n][m]); 64 } 65 }