[R48G]子集求和
题目
给定长度为 的数组 。对于任意一个子区间构成的可重集合 ,定义 为子集 中「出现次数为奇数」的元素之和,定义 。有 次询问,每次给定区间 ,求该区间对应集合的 。
对于 的数据,,,。
思路
设区间内不同取值为 , 在区间内的出现次数为 ,区间长度 。利用
对全部 个子集求和。对每个值 ,从它的 份副本里选若干份,奇数份的方案数为 。于是:
- :贡献 ;
- (两个不同值):贡献 。
求和得
样例验证:,,不同值 ,,,;,,不同值 ,,,,与样例一致。
因此关键只在于:对每个询问区间 快速求出 区间内不同元素之和 与 区间内不同元素平方之和 。
这是经典的「区间不同元素求和」问题,离线处理:把询问按右端点 升序(直接按 分桶即可),用一个支持单点修改、区间求和的树状数组,对每个值只在其「最近出现位置」上累加贡献。从左到右扫到位置 时,若 之前在 出现过,则先把 处的贡献撤销,再把当前位置 加入(值为 ,平方贡献为 )。查询时对树状数组做 ,得到的恰好是区间 内 不同 元素之和(平方同理)。
最后注意 的 可能为负( 时),直接用 代替 即可, 由费马小定理 给出,全程规避负指数。
复杂度
- 时间复杂度:。
- 空间复杂度:。
仓颉实现
查看仓颉实现
import std.convert.*
import std.collection.*
import std.env.*
const MOD: Int64 = 1000000007
func modpow(base0: Int64, exp0: Int64): Int64 {
var base = base0 % MOD
var exp = exp0
var res: Int64 = 1
while (exp > 0) {
if ((exp & 1) == 1) {
res = (res * base) % MOD
}
base = (base * base) % MOD
exp = exp / 2
}
return res
}
// BIT 维护模意义下的加法
class BIT {
let n: Int64
let t: Array<Int64>
public init(n: Int64) {
this.n = n
this.t = Array<Int64>(n + 1, { _ => 0 })
}
public func add(i: Int64, v: Int64): Unit {
var idx = i
while (idx <= n) {
t[idx] = (t[idx] + v) % MOD
idx += idx & (-idx)
}
}
public func sum(i: Int64): Int64 {
var res: Int64 = 0
var idx = i
while (idx > 0) {
res = (res + t[idx]) % MOD
idx -= idx & (-idx)
}
return res
}
}
main() {
let reader = getStdIn()
let first = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
let n = first[0]
let m = first[1]
let a = Array<Int64>(n + 1, { _ => 0 })
let arrLine = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
var idx: Int64 = 1
while (idx <= n) {
a[idx] = arrLine[idx - 1]
idx += 1
}
// 读取询问,按右端点分桶
let ql = Array<Int64>(m, { _ => 0 })
let qr = Array<Int64>(m, { _ => 0 })
// bucket[r] = 以 r 为右端点的询问下标列表
let bucket = Array<ArrayList<Int64>>(n + 1, { _ => ArrayList<Int64>() })
var i: Int64 = 0
while (i < m) {
let q = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
ql[i] = q[0]
qr[i] = q[1]
bucket[qr[i]].add(i)
i += 1
}
// 预处理 2 的幂
let pw2 = Array<Int64>(n + 2, { _ => 0 })
pw2[0] = 1
var k: Int64 = 1
while (k <= n + 1) {
pw2[k] = (pw2[k - 1] * 2) % MOD
k += 1
}
let inv2 = modpow(2, MOD - 2)
let inv4 = (inv2 * inv2) % MOD
let bitSum = BIT(n)
let bitSq = BIT(n)
let lastPos = HashMap<Int64, Int64>()
let ans = Array<Int64>(m, { _ => 0 })
var pos: Int64 = 1
while (pos <= n) {
let v = a[pos]
let vm = v % MOD
let vsq = (vm * vm) % MOD
if (lastPos.contains(v)) {
let lp = lastPos[v]
bitSum.add(lp, MOD - vm)
bitSq.add(lp, MOD - vsq)
}
bitSum.add(pos, vm)
bitSq.add(pos, vsq)
lastPos[v] = pos
// 处理右端点为 pos 的所有询问
let lst = bucket[pos]
for (qi in lst) {
let l = ql[qi]
let len = pos - l + 1
let s1 = ((bitSum.sum(pos) - bitSum.sum(l - 1)) % MOD + MOD) % MOD
let s2 = ((bitSq.sum(pos) - bitSq.sum(l - 1)) % MOD + MOD) % MOD
var g = (s1 * s1) % MOD
g = (g + s2) % MOD
// G = 2^(len-2) * g;用 2^len * inv4 代替 2^(len-2),避免负指数
let factor = (pw2[len] * inv4) % MOD
g = (g * factor) % MOD
ans[qi] = g
}
pos += 1
}
var j: Int64 = 0
while (j < m) {
println(ans[j])
j += 1
}
}