龙虎斗

GitHub跳转原题关系图返回列表

把龙虎气势差折叠成带符号的 delta = Σ c_i×(m-i),再枚举投放位置 p 取 |delta + s_2×(m-p)| 最小的最小编号。

OJ: roj

题目 ID: 3655

难度:入门

标签:枚举前缀和思想模拟python

创建: 2026-10-02 13:30

更新: 2026-10-06 16:00

题目描述

线段上有 n 个兵营,第 i 个有 c_i 位工兵。以 m 号兵营为界,左侧(p<m)属龙方,右侧(p>m)属虎方,m 号不属于任何一方;兵营 p 的气势为 c_p × |p-m|,一方势力为所属兵营气势之和。先在 p_1 号兵营增加 s_1 位工兵,再把 s_2 位工兵全部投放到某个兵营 p_2,使龙、虎两方势力之差的绝对值最小,输出最优的 p_2(并列取最小编号)。

输入第一行 n,第二行 n 个整数 c_i,第三行四个整数 m, p_1, s_1, s_2;输出一个整数 p_2。数据范围:1 < m < n ≤ 10^5,c_i, s_1, s_2 ≤ 10^9。样例 1 输入 6 / 2 3 2 3 2 3 / 4 6 5 2,输出 2;样例 2 输入 6 / 1 1 1 1 1 16 / 5 4 1 1,输出 1。

思路

把龙虎气势差折叠成一个带符号的和 δ = Σ c_i×(m-i):p<m 的项为正(龙),p>m 的项为负(虎),p=m 恰为 0。投放 s_2 人到 p 后的差距为 |δ + s_2×(m-p)|,从左到右扫一遍取最小值,并列取最小编号;初值取 p=m 即“不改变气势”。气势差可达约 2×10^19,需用 __int128。

参考代码

Python 版:

python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-07-05 21:47
# update_at: 2026-07-05 21:47

import sys


def momentum(pos: int, m: int, count: int) -> int:
    """count 位工兵落在 pos 号兵营产生的带符号气势:龙方为正、虎方为负。"""
    return count * (m - pos)


def best_camp(delta: int, m: int, s2: int, n: int) -> int:
    """在气势差 delta(龙-虎)基础上投放 s2 人,返回使 |delta| 最小的兵营编号。"""
    ans, best = m, abs(delta)  # 不投也允许(p2 = m),初值就是"投到 m"的效果
    for p in range(1, n + 1):
        cur = abs(delta + momentum(p, m, s2))
        if cur < best:
            ans, best = p, cur
    return ans


def solve() -> None:
    data = iter(map(int, sys.stdin.buffer.read().split()))
    n = next(data)
    c = [next(data) for _ in range(n)]
    m, p1, s1, s2 = next(data), next(data), next(data), next(data)

    c[p1 - 1] += s1  # 天降神兵:先落位 s1

    # 龙方气势为正、虎方气势为负,二者之差就是 delta(m 号兵营两侧贡献都为 0)
    delta = sum(momentum(pos, m, c[pos - 1]) for pos in range(1, n + 1))
    print(best_camp(delta, m, s2, n))


if __name__ == "__main__":
    solve()

C++ 版(同一算法):

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-10-06 16:00
 * update_at: 2026-10-06 16:00
 */
#include <cstdio>
using namespace std;

typedef long long ll;

const int MAXN = 100005;
ll c[MAXN]; // c[i] 表示第 i 个兵营的工兵数(包含天降神兵后的数量)

// 气势差最大约 c_i * |m-i| * n ≈ 2e19,超过 long long 上限,故用 __int128 保存。
// 返回 __int128 的绝对值。
__int128 i128_abs(__int128 x) {
    if (x < 0) return -x;
    return x;
}

// 返回 count 位工兵落在 pos 号兵营产生的带符号气势:龙方为正、虎方为负。
__int128 signed_momentum(ll count, ll pos, ll m) {
    __int128 cnt = count;
    return cnt * (m - pos);
}

int main() {
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%lld", &c[i]);
    }
    ll m, p1, s1, s2;
    scanf("%lld %lld %lld %lld", &m, &p1, &s1, &s2);

    c[p1] += s1; // 天降神兵:先在 p1 号兵营补上 s1 人

    // delta = 龙方气势 - 虎方气势 = sum c[i] * (m - i)
    // p < m 时为正(龙),p > m 时为负(虎),p = m 时恰为 0
    __int128 delta = 0;
    for (int i = 1; i <= n; i++) {
        delta += signed_momentum(c[i], i, m);
    }

    // 枚举投放位置,取使 |delta + s2*(m-p)| 最小的最小编号;
    // 初值取 p2 = m(相当于不改变气势),并保证并列时保留更左的编号
    ll ans = m;
    __int128 best = i128_abs(delta);
    for (int p = 1; p <= n; p++) {
        __int128 cur = i128_abs(delta + signed_momentum(s2, p, m));
        if (cur < best) {
            best = cur;
            ans = p;
        }
    }

    printf("%lld\n", ans);
    return 0;
}