[R58C] 猴子排序


数据规模:1≤n,m≤2×1051 \le n, m \le 2 \times 10^5,1≤ai,bi≤n1 \le a_i, b_i \le n,ai≠bia_i \ne b_i,PP 为排列。

思路

关键是要判断「交换 PaiP_{a_i} 与 PbiP_{b_i} 后逆序对是否减少」。设 a<ba < b,x=Pax = P_a,y=Pby = P_b,记 cc 为下标在 aa 与 bb 之间、且值严格落在 min⁡(x,y)\min(x, y) 与 max⁡(x,y)\max(x, y) 之间的元素个数。交换只影响与位置 aa、bb 有关的逆序对:

  • 值小于 min⁡(x,y)\min(x, y) 或大于 max⁡(x,y)\max(x, y) 的中间元素,交换前后与 xx、yy 恰好一增一减,贡献抵消;
  • 值落在两者之间的 cc 个元素:若 x<yx < y,交换前 xx 在它们左侧、yy 在右侧均不构成逆序对,交换后 yy 移到左侧、xx 移到右侧各多 11 个,合计 +2c+2c;若 x>yx > y 则对称地合计 −2c-2c;
  • xx 与 yy 本身:x<yx < y 时交换后 yy 在 xx 左侧,逆序对 +1+1;x>yx > y 时 −1-1。

于是交换前后逆序对变化量:

Δ={2c+1>0,x<y−(2c+1)<0,x>y\Delta = \begin{cases} 2c + 1 > 0, & x < y \\ -(2c + 1) < 0, & x > y \end{cases}

恒正或恒负,与 cc 无关!因此策略退化为:当且仅当左侧位置的值大于右侧位置的值时交换。注意 ai,bia_i, b_i 没有保证大小关系,先令 L=min⁡(ai,bi)L = \min(a_i, b_i),R=max⁡(ai,bi)R = \max(a_i, b_i),若 PL>PRP_L > P_R 则交换,否则不交换;交换后继续模拟即可。最后检查排列是否严格递增。

复杂度:时间 O(n+m)O(n + m),空间 O(n)O(n)。

仓颉实现

查看仓颉实现
import std.env.*
import std.convert.*

main() {
    let reader = getStdIn()
    let nm = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ q: String => Int64.parse(q) })
    let n = nm[0]
    let m = nm[1]
    let p = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ q: String => Int64.parse(q) })
    let swapped = Array<Bool>(m, { _ => false })
    for (i in 0..m) {
        let ab = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ q: String => Int64.parse(q) })
        let a = ab[0] - 1
        let b = ab[1] - 1
        let l = if (a < b) { a } else { b }
        let r = if (a < b) { b } else { a }
        if (p[l] > p[r]) {
            let t = p[l]
            p[l] = p[r]
            p[r] = t
            swapped[i] = true
        }
    }
    var win = true
    for (i in 1..n) {
        if (p[i] <= p[i - 1]) {
            win = false
            break
        }
    }
    println(if (win) { "Win" } else { "Lose" })
    for (i in 0..m) {
        println(if (swapped[i]) { "Yes" } else { "No" })
    }
}