[R7F] 连续区间
- 难度 提高
- 时限 2s
- 空限 512m
- 分块众数离线
数据规模:,,。
思路
要让 成为连续区间,未被修改的元素 ()必须满足 ,即 。所以不修改的元素 取值相同。令 ,则 中众数的出现次数。问题归约为静态区间众数出现次数。
采用分块。设块长为 ,块数为 。预处理:
- :第 块这一段的众数频次。对每个起点块 ,右端点向右扫,累加计数并维护当前众数频次。
- 对每个离散值 ,保存它在原数组中所有出现位置(已有序)。
回答询问 (记左端块 ,右端块 ):若同块直接暴力统计;否则答案为「中间整块 的众数频次」与「左右散块中每个出现过的值 在 中的总出现次数」取最大。散块去重用时间戳数组而非哈希表以减小常数; 在区间内的次数用位置数组上二分(upper - lower)得到。
复杂度:时间 ,空间 。
仓颉实现
import std.convert.*
import std.env.*
import std.sort.*
import std.collection.*
main(): Int64 {
let reader = getStdIn()
let l1 = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p => Int64.parse(p) })
let n = l1[0]
let m = l1[1]
let a = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p => Int64.parse(p) })
let b = Array<Int64>(n, { i => a[i] - i - 1 })
var bsz: Int64 = 1
while ((bsz + 1) * (bsz + 1) <= n) {
bsz += 1
}
if (bsz < 1) {
bsz = 1
}
let B = bsz
let nb = (n + B - 1) / B
// 离散化 B
let sb = Array<Int64>(n, { i => b[i] })
sort(sb)
var uid: Int64 = 0
let idmap = HashMap<Int64, Int64>()
for (i in 0..n) {
if (i == 0 || sb[i] != sb[i - 1]) {
idmap[sb[i]] = uid
uid += 1
}
}
let K = uid
let bid = Array<Int64>(n, { i => idmap.get(b[i]).getOrThrow() })
let pos = ArrayList<ArrayList<Int64>>(K, { _ => ArrayList<Int64>() })
for (i in 0..n) {
pos[bid[i]].add(i)
}
let blk = Array<Int64>(n, { i => i / B })
let nbI = Int64(nb)
let h = Array<Array<Int64>>(nbI, { _ => Array<Int64>(nbI, { _ => 0 }) })
for (i in 0..nb) {
var cnt = Array<Int64>(K, { _ => 0 })
var mx: Int64 = 0
for (j in i..nb) {
let lo = j * B
let hi = if (((j + 1) * B) < n) { (j + 1) * B } else { n }
for (x in lo..hi) {
let c = bid[x]
cnt[c] += 1
if (cnt[c] > mx) {
mx = cnt[c]
}
}
h[i][j] = mx
}
}
func lower(arr: ArrayList<Int64>, v: Int64): Int64 {
var l: Int64 = 0
var r: Int64 = arr.size
while (l < r) {
let mid = (l + r) / 2
if (arr[mid] < v) {
l = mid + 1
} else {
r = mid
}
}
return l
}
func upper(arr: ArrayList<Int64>, v: Int64): Int64 {
var l: Int64 = 0
var r: Int64 = arr.size
while (l < r) {
let mid = (l + r) / 2
if (arr[mid] <= v) {
l = mid + 1
} else {
r = mid
}
}
return l
}
let out = StringBuilder()
let seenStamp = Array<Int64>(K, { _ => -1 })
let curCnt = Array<Int64>(K, { _ => 0 })
var stamp: Int64 = 0
for (_ in 0..m) {
let lr = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p => Int64.parse(p) })
let L = lr[0] - 1
let R = lr[1] - 1
let bl = blk[L]
let br = blk[R]
var ans: Int64 = 0
if (bl == br) {
stamp += 1
for (x in L..=R) {
let c = bid[x]
if (seenStamp[c] != stamp) {
seenStamp[c] = stamp
curCnt[c] = 1
} else {
curCnt[c] += 1
}
if (curCnt[c] > ans) {
ans = curCnt[c]
}
}
} else {
let lp = bl + 1
let rp = br - 1
var base: Int64 = 0
if (lp <= rp) {
base = h[lp][rp]
}
ans = base
stamp += 1
let lrEnd = (bl + 1) * B - 1
for (x in L..=lrEnd) {
let c = bid[x]
if (seenStamp[c] != stamp) {
seenStamp[c] = stamp
let arr = pos[c]
let tot = upper(arr, R) - lower(arr, L)
if (tot > ans) {
ans = tot
}
}
}
let rl = br * B
for (x in rl..=R) {
let c = bid[x]
if (seenStamp[c] != stamp) {
seenStamp[c] = stamp
let arr = pos[c]
let tot = upper(arr, R) - lower(arr, L)
if (tot > ans) {
ans = tot
}
}
}
}
let res = (R - L + 1) - ans
out.append(res)
out.append("\n")
}
print(out.toString())
return 0
}
要点:
- 转化为 的静态区间众数频次问题,是经典分块场景。
- 散块去重用时间戳数组代替哈希集合,常数更小,是把 跑进时限的关键。