原题链接:https://www.luogu.org/problem/show?pid=2822#sub
做一次不爽一次。仅仅是对我去年考场上没想起来c(i,j) = c(i-1,j) + c(i-1,j-1)的垃圾行为而感到绝望的愤怒。
(LaTeX用得还暂时不熟练,各位谅解)
我实在是想不起来我当时是有多大的勇气,在这道题写完暴力不管不问的情况下就去刚T2T3。
如果您还没学排列组合,那这道题可能暂时不适合您来做。
如果您学过排列组合(高中数学选修2-3相关内容),应该对一个式子不陌生,就是我刚才在上面写的那个c(i,j) = c(i-1,j) + c(i-1,j-1)。
这是组合数的一个性质,要完成这道题需要用这个式子进行一下预处理。
暴力思想很简单,暴力算阶乘,暴力算组合。如果在暴力时稍加改进会如何?
看到n只有2000,所以可以考虑先把这个范围内的所有c[i][j]都先求出来,就用那个公式,全都推出来就好,边界是c[i][0] = c[i][i] = 1(依据组合数的性质)
这样我们就有了一个表。如果您学过排列组合,可以输出一下看看,这个表长什么样?
这可是杨辉三角啊!
实际上,为了求解方便,在推的时候可以把值对k取模,这样推出来后的c[i][j]如果是0,那么这个就是k的倍数。
按要求输出即可,这题到此结束。
哦?是吗?那你可只有90分啊。
后面那10分卡在哪里了?输出。
如果不加任何处理,在输出的时候我们也是要遍历一次c数组的,这个操作比较费时间。
既然c数组可以打表,答案为什么不可以?
令s[i][j]表示在所有的c(i,j) (1≤j≤i)的里面,为k的倍数的有多少个,那么处理数组的时候就是s[i][j] = s[i][j-1] ,每找到一个s[i][j]为0就让值+1。
按要求输出即可,这题到此结束。
行了,这次可以A了。
参考代码:
1 #include <iostream> 2 #define maxn 2005 3 using namespace std; 4 int c[maxn][maxn]; 5 int s[maxn][maxn]; 6 int ans[maxn][maxn]; 7 int t,k,n,m; 8 int main(){ 9 cin >> t >> k; 10 for (int i=1;i<maxn;i++){ 11 c[i][0] = 1; 12 c[i][i] = 1; 13 for (int j=1;j<i;j++) 14 c[i][j] = (c[i-1][j] + c[i-1][j-1]) % k; 15 } 16 17 for (int i=1;i<maxn;i++) 18 for (int j=1;j<=i;j++){ 19 s[i][j] = s[i][j-1]; 20 if (c[i][j] == 0) 21 s[i][j]++; 22 } 23 24 for (int time=1;time<=t;time++){ 25 cin >> n >> m; 26 27 int ans = 0; 28 for (int i=1;i<=n;i++){ 29 int j = min(i,m); 30 ans += s[i][j]; 31 } 32 cout << ans << endl; 33 34 35 } 36 return 0; 37 }