[R54C]切割圆


对于 100%100\% 的数据,1n2×1051 \le n \le 2 \times 10^50ai1090 \le a_i \le 10^9

思路

环上共 2n2n 个元素,一条直线切下后两部分各恰为 nn 个连续元素,所以所有可能的切法就是枚举一个起点 s[0,2n)s \in [0, 2n),取环上从 ss 开始的连续 nn 个元素作为一部分 bb,剩余 nn 个作为另一部分 cc

设起点 ss 对应的连续 nn 元素之和为 sum1sum_1,整环总和为 totaltotal,则另一部分之和为 totalsum1total - sum_1。目标是最大化:

sum1(totalsum1)sum_1 \oplus (total - sum_1)

所有切法只差在 sum1sum_1 上,而 sum1sum_1 随起点 ss 每次右移一格,仅发生「减去离开窗口的 as1a_{s-1}、加上进入窗口的 a(s+n1)mod2na_{(s+n-1) \bmod 2n}」的增量变化。因此用 滑动窗口O(n)O(n) 内求出全部 2n2nsum1sum_1,逐一计算异或值取最大即可。

复杂度

  • 时间:O(n)O(n),滑动窗口每个元素进出各一次。
  • 空间:O(n)O(n),存储 2n2n 个数。

仓颉实现

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

main(): Int64 {
    let reader = getStdIn()
    let n = Int64.parse(reader.readln().getOrThrow())
    let a = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
    let m = n * 2
    var total: Int64 = 0
    for (i in 0..m) {
        total += a[i]
    }
    // 滑动窗口大小为 n,初始窗口为 a[0..n-1]
    var sum1: Int64 = 0
    for (i in 0..n) {
        sum1 += a[i]
    }
    var ans: Int64 = sum1 ^ (total - sum1)
    var s: Int64 = 1
    while (s < m) {
        // 起点为 s 的窗口:减去 a[s-1],加上 a[(s+n-1) mod m]
        let outIdx = s - 1
        let inIdx = (s + n - 1) % m
        sum1 = sum1 - a[outIdx] + a[inIdx]
        let cur = sum1 ^ (total - sum1)
        if (cur > ans) {
            ans = cur
        }
        s++
    }
    println(ans)
    return 0
}