[R15D] 二维异或和
- 难度 普及/提高-
- 时限 1s
- 空限 512m
- 位运算前缀和按位计数
数据规模:,,,。
思路
直接对每个询问两重枚举 计算复杂度是 ,无法承受。关键观察是 按位独立:计算 时,二进制下的每一位互不影响。单独看第 位, 在该位为 ,当且仅当 和 在该位不同。
设区间 内第 位为 的元素个数为 ,则该位为 的元素个数为 。要让 在第 位为 ,需要 在该位一 一 。有序对 中 为 、 为 有 个,反向( 为 、 为 )同样有 个,合计 个,每个贡献 。因此整个询问的答案为
现在的问题是高效求出 ,即区间 内第 位为 的元素个数。维护前缀和 表示前缀 中第 位为 的个数,则 ,每次询问 。
由于 ,只需枚举 位。复杂度:时间 ,空间 。
仓颉实现
import std.env.*
import std.convert.*
main(): Int64 {
let reader = getStdIn()
let line1 = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
let n = line1[0]
let q = line1[1]
let a = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
// A_i <= 10^6 < 2^20, 需要 20 位 (0..19)
let B = 20
// cnt[b][i]: 前缀 i 中第 b 位为 1 的个数
let cnt = Array<Array<Int64>>(B, { _ => Array<Int64>(n + 1, { _ => 0 }) })
for (b in 0..B) {
let mask = Int64(1) << b
let row = cnt[b]
for (i in 1..(n + 1)) {
let add = if ((a[i - 1] & mask) != 0) { 1 } else { 0 }
row[i] = row[i - 1] + add
}
}
let sb = StringBuilder()
var idx = 0
while (idx < q) {
let parts = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
let l = parts[0]
let r = parts[1]
let len = r - l + 1
var ans = Int64(0)
for (b in 0..B) {
let row = cnt[b]
let x = row[r] - row[l - 1]
ans += 2 * x * (len - x) * (Int64(1) << b)
}
sb.append(ans)
if (idx < q - 1) {
sb.append(" ")
}
idx++
}
println(sb)
return 0
}
要点:
- 逐位构建前缀和: 表示 中第 位为 的个数,用掩码
mask = 1 << b判断每一位。 - 询问时对每一位用差分 取出区间内第 位为 的个数 ,累加 。系数 来自有序对: 中 为 、 为 有 个,反向同理,两部分配对数相同,合起来翻倍。
- 区间长度 ,答案最大约 ,用
Int64足够。