[R63E] 这是一个01串题2

  • 难度 普及+/提高
  • 时限 1s
  • 空限 512m
  • 贪心前缀和

数据规模:1n,Q2×1051 \le n, Q \le 2 \times 10^50kin0 \le k_i \le n

思路

把字符 0 翻成 1 只会让字典序变大,且对消除相邻的 1 毫无帮助,因此最优方案的每次翻转一定把某个 1 翻成 0;而把 1 翻成 0 永远不会破坏「不存在相邻 1」的性质。问题等价于:从 ss 中选出至多 kk 个位置(必须是 1)翻成 0,使剩余字符串无相邻 1,且字典序最小。

考虑一段连续的 1,设其长度为 LL,至少要翻 L/2\lfloor L/2 \rfloor 个位置才能断开所有相邻对。在最小翻转过数下,为保证字典序最小,翻法唯一确定:

  • LL 为偶数:翻段内奇数偏移位,如 111101011111 \to 0101
  • LL 为奇数:翻段内偶数偏移位,如 111111010111111 \to 10101

各段互不影响,记最少操作总数为 need=L/2\text{need} = \sum \lfloor L/2 \rfloor。若 k<needk < \text{need},无法得到合法串,输出 -1

kneedk \ge \text{need} 时,先做这 need\text{need} 次翻转得到串 tt^*。多余的操作次数越多,字典序越小,因此每次多余操作都把 tt^*最靠左的 1 翻成 0。设多余操作次数为 e=kneede = k - \text{need},若 ee 不小于 tt^*1 的总数 mm,则 tkt_k 为全 0 串,任何询问答案都是 0;否则记 pptt^* 中从左往右第 ee1 的位置(e=0e = 0 时令 p=0p = 0),那么 tkt_k 中位置 p\le p 处全部为 0,位置 >p> p 处与 tt^* 完全一致。

因此询问 (k,l,r)(k, l, r) 的答案就是 tt^*1 在区间 [max(l,p+1),r][\max(l, p+1), r] 中的个数,用 tt^* 的前缀和 O(1)O(1) 计算即可;若 rpr \le p 则答案为 0

复杂度

预处理扫描一次 O(n)O(n),每次询问 O(1)O(1),总复杂度 O(n+Q)O(n + Q)

仓颉实现

import std.env.*
import std.convert.*
import std.collection.*

func solve(reader: ConsoleReader): Unit {
    let nq = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
    let n = nq[0]
    let qnum = nq[1]
    let s = reader.readln().getOrThrow()
    let chars = s.toRuneArray()
    let isOne = Array<Int64>(n, { _ => 0 })
    let kept = ArrayList<Int64>()
    var need: Int64 = 0
    var i: Int64 = 0
    while (i < n) {
        if (chars[i] == r'1') {
            let st = i
            while (i < n && chars[i] == r'1') {
                i += 1
            }
            let en = i - 1
            let len = en - st + 1
            if (len % 2 == 0) {
                var j = st
                while (j <= en) {
                    need += 1
                    j += 2
                }
                j = st + 1
                while (j <= en) {
                    isOne[j] = 1
                    kept.add(j + 1)
                    j += 2
                }
            } else {
                var j = st + 1
                while (j <= en) {
                    need += 1
                    j += 2
                }
                j = st
                while (j <= en) {
                    isOne[j] = 1
                    kept.add(j + 1)
                    j += 2
                }
            }
        } else {
            i += 1
        }
    }
    let pref = Array<Int64>(n + 1, { _ => 0 })
    var acc: Int64 = 0
    var j: Int64 = 1
    while (j <= n) {
        acc += isOne[j - 1]
        pref[j] = acc
        j += 1
    }
    let m = kept.size
    var q: Int64 = 0
    while (q < qnum) {
        let qs = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
        let k = qs[0]
        let l = qs[1]
        let r = qs[2]
        if (k < need) {
            println("-1")
        } else {
            let e = k - need
            if (e >= m) {
                println("0")
            } else {
                var p: Int64 = 0
                if (e > 0) {
                    p = kept.get(e - 1).getOrThrow()
                }
                if (r <= p) {
                    println("0")
                } else {
                    var left = l - 1
                    if (p > left) {
                        left = p
                    }
                    let ans = pref[r] - pref[left]
                    println(ans.toString())
                }
            }
        }
        q += 1
    }
}

main(): Int64 {
    let reader = getStdIn()
    solve(reader)
    return 0
}