[luogu2822] 组合数问题
题面
大家只要知道这样一个公式就行了:
\[C_n ^ m = C_{n - 1} ^ {m} + C_{n - 1} ^ {m - 1}
\]
这样的话, 就是求\(\sum_{i = 0} ^ {n}\sum_{j = 0}^{min(i, m)}[k|C_i ^ j]\)的值了...
考虑使用离线算法, 先读入询问, 然后将询问以\(n\)为第一关键字, \(m\)为第二关键字排序, 利用二维前缀和优化, 时间复杂度为\(O(T *max(ask[i])^2)\), 空间复杂度为\(O(max(ask[i]) ^ 2)\), 观察到, 当前行只与前面一行有关, 考虑使用滚动数组优化, 进一步将代码空间复杂度优化至\(O(max(ask[i]))\), 滚动数组真是好东西啊...
具体代码
//在线做法, 虽然说上面说的是离线, 但是只有滚动数组是离线, 这个是在线滴
#include <iostream>
#include <cstring>
#include <cstdio>
#define N 10005
using namespace std;
int t, k, mx, ans[2005][2005], c[2005][2005];
inline int read()
{
int x = 0, w = 1;
char c = getchar();
while(c < '0' || c > '9') { if (c == '-') w = -1; c = getchar(); }
while(c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
return x * w;
}
int main()
{
t = read(); k = read();
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 - 1] + c[i - 1][j]) % k;
ans[i][j] = ans[i][j - 1] + ans[i - 1][j] - ans[i - 1][j - 1] + (c[i][j] == 0);
//考虑到组合数中C(n,m),m恒不大于n, 所以呢就可以用上二维前缀和了, 毕竟多余出来的东西都是零啊
}
ans[i][i + 1] = ans[i][i]; //这个地方大家画一个杨辉三角就可以理解了, 我在这也讲的不是很清楚, 总之就是杨辉三角的下一层比当前层会多一个, 这一个就需要用这个ans[i][i+1]去更新
}
while(t--)
{
int n = read(), m = read();
printf("%d\n", m > n ? ans[n][n] : ans[n][m]);
}
return 0;
}
//离线来了, 好像我在luogu上看了看没有写滚动数组的
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#define N 10005
using namespace std;
int t, k, mx, sum[2][2005], c[2][2005], top = 1, ans[N];
struct node
{
int n, m, id;
} ask[N];
inline int read()
{
int x = 0, w = 1;
char c = getchar();
while(c < '0' || c > '9') { if (c == '-') w = -1; c = getchar(); }
while(c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
return x * w;
}
bool cmp(node a, node b) { return (a.n < b.n) || ((a.n == b.n) && (a.m < b.m)); }
int main()
{
t = read(); k = read();
c[0][0] = c[1][0] = c[1][1] = 1;
for(int i = 1; i <= t; i++) { ask[i].n = read(); ask[i].m = read(); ask[i].id = i; mx = max(mx, ask[i].n); }
sort(ask + 1, ask + t + 1, cmp);
while(ask[top].n == 1) { ans[ask[top].id] = 0; top++; }
for(int i = 2; i <= mx; i++)
{
int opt = i % 2;
c[opt][0] = 1;
for(int j = 1; j <= i; j++)
{//根据奇偶性来滚动
c[opt][j] = (c[opt ^ 1][j - 1] + c[opt ^ 1][j]) % k;
sum[opt][j] = sum[opt][j - 1] + sum[opt ^ 1][j] - sum[opt ^ 1][j - 1] + (c[opt][j] == 0); //滚动数组其实就是相当于一个蛇皮前缀和, 记录下了之前的结果, 并在当前离线排序完后的数组中选择与当前枚举行相同的询问并更新答案即可
}
sum[opt][i + 1] = sum[opt][i];//跟上面是一样的
while(ask[top].n == i) { ans[ask[top].id] = sum[opt][ask[top].m > ask[top].n ? ask[top].n : ask[top].m]; top++; }
}
for(int i = 1; i <= t; i++) printf("%d\n", ans[i]);
return 0;
}