1 条题解
-
0
一、题意理解
给定长度为 的序列 ,。
次询问区间 中有多少种数字出现正偶数次。
强制在线:当前询问的 由上一问答案 计算得到。
二、样例分析
输入:
5 3 5 1 2 2 3 1 0 4 1 2 2 2 2 3 3 5数组:
[1, 2, 2, 3, 1]-
第一次询问 → 实际 整个数组:
- 1出现2次(偶数)
- 2出现2次(偶数)
- 3出现1次(奇数) → 偶数次种类数 = 2(数字1和2)→ ans=2
-
第二次:, → , → :
- 数字3出现1次,数字1出现1次 → 偶数次种类数=0
-
第三次:, → , → :
- 数字2出现1次 → 偶数次种类数=0
-
第四次:, → , → :
- 数字2出现1次,数字3出现1次 → 偶数次种类数=0
-
第五次:, → , → 交换得 :
- 数字1出现1次,数字2出现2次,数字3出现1次 → 偶数次种类数=1(数字2)
输出:
2 0 0 0 1
三、关键难点
- 强制在线 → 不能用莫队
- ,需要高效算法
- 询问区间出现偶数次的数字种类数
四、常见解法:分块
将序列分成 块,每块大小 。
预处理:
- :前 块中数字 的出现次数
- :第 块到第 块的答案(偶数次种类数)
1. 预处理
可以 预处理。
2. 预处理
表示块 到块 的答案:
- 初始
- 加入第 块,更新数字出现次数的奇偶性
复杂度
3. 回答询问
对于询问 :
- 找到 所在块 , 所在块
- 如果 ,暴力计算
- 否则:
- 答案初始为
- 加入左边残块和右边残块,更新数字出现次数奇偶性
- 用 数组快速得到整块中出现次数,结合残块更新
复杂度 每次询问。
五、奇偶性维护技巧
我们只关心每个数字出现次数的奇偶性,可以用一个 bitset 或 int 位压缩来记录。
但 ,bitset 大小 位,可以接受。
六、算法步骤
- 分块,块大小
- 预处理 数组
- 预处理 数组
- 对每个询问:
- 根据上次答案计算真实
- 用分块方法求区间偶数次种类数
- 输出答案
七、代码框架(C++)
#include <bits/stdc++.h> using namespace std; int a[100010], l[410], r[410], cnt[410][100010], n, c, m; short b[100010], f[100010], ans[410][410], num, len; int query(int ql, int qr) { int anss = 0; if (b[ql] + 1 >= b[qr]) { for (int i = ql; i <= qr; i++) { f[a[i]]++; if (f[a[i]] & 1) anss -= (f[a[i]] >= 3); else anss++; } for (int i = ql; i <= qr; i++) f[a[i]]--; } else { int pl = b[ql] + 1, pr = b[qr] - 1; anss = ans[pl][pr]; for (int i = ql; i < l[pl]; i++) f[a[i]] = cnt[pr][a[i]] - cnt[pl - 1][a[i]]; for (int i = r[pr] + 1; i <= qr; i++) f[a[i]] = cnt[pr][a[i]] - cnt[pl - 1][a[i]]; for (int i = ql; i < l[pl]; i++) { f[a[i]]++; if (f[a[i]] & 1) anss -= (f[a[i]] >= 3); else anss++; } for (int i = r[pr] + 1; i <= qr; i++) { f[a[i]]++; if (f[a[i]] & 1) anss -= (f[a[i]] >= 3); else anss++; } for (int i = ql; i < l[pl]; i++) f[a[i]] = 0; for (int i = r[pr] + 1; i <= qr; i++) f[a[i]] = 0; } return anss; } int main() { ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); int ql, qr, lans = 0; cin >> n >> c >> m; len = sqrt(n); num = (int)ceil(n * 1.0 / len); for (int i = 1; i <= n; i++) cin >> a[i], b[i] = (i - 1) / len + 1, cnt[b[i]][a[i]]++; for (int i = 1; i <= num; i++) l[i] = r[i - 1] + 1, r[i] = min(n, l[i] + len - 1); for (int i = 1; i <= num; i++) for (int j = 0; j <= c; j++) cnt[i][j] += cnt[i - 1][j]; for (int i = 1; i <= num; i++) { for (int j = i; j <= num; j++) { ans[i][j] = ans[i][j - 1]; for (int k = l[j]; k <= r[j]; k++) { f[a[k]]++; if (f[a[k]] & 1) ans[i][j] -= (f[a[k]] >= 3); else ans[i][j]++; } } memset(f, 0, sizeof(f)); } for (int i = 1; i <= m; i++) { cin >> ql >> qr; ql = (ql + lans) % n + 1; qr = (qr + lans) % n + 1; if (ql > qr) swap(ql, qr); cout << (lans = query(ql, qr)) << '\n'; } return 0; }
八、复杂度分析
- 预处理:
- 每次询问:
- 总复杂度:,可过
九、总结
本题的关键在于:
- 理解强制在线条件下的区间查询问题。
- 使用分块算法平衡预处理和查询复杂度。
- 维护每个数字出现次数的奇偶性来统计偶数次种类数。
- 注意边界块的处理和临时数组的清空。
-
- 1
信息
- ID
- 4969
- 时间
- 1500ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 2
- 已通过
- 1
- 上传者