[NOIP 2018 普及组] 龙虎斗

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

先算出加入 s1 后的双方气势差,再枚举第二次投放位置,比较加入 s2 后的差值绝对值。

OJ: luogu

题目 ID: P5016

难度:普及-

标签:模拟枚举

日期: 2026-06-19 01:07

题意

棋盘上有 n 个兵营,以第 m 个兵营为分界:

  • 左边属于龙
  • 右边属于虎
  • m 个兵营不属于任何一方

一个兵营对某一方贡献的气势等于:

工兵数 × 到 m 的距离

先有一次固定事件:往 p1 号兵营加入 s1 位工兵。
然后你可以再选择一个兵营 p2,把 s2 位工兵全部放进去。

要求让最后龙方和虎方气势差的绝对值尽可能小,输出这个 p2

思路

先看最直观的做法:

枚举每一个 p2,然后重新统计一遍加入 s1s2 之后双方的总气势,比较差值绝对值。

这个办法很直接,也方便拿来对拍:

cpp
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 205;

int n, m, p1;
long long s1, s2;
long long c[MAXN];

long long calc_diff_after_choose(int p2) {
    long long dragon = 0;
    long long tiger = 0;

    for (int i = 1; i <= n; i++) {
        long long cnt = c[i];

        if (i == p1) {
            cnt += s1;
        }
        if (i == p2) {
            cnt += s2;
        }

        if (i < m) {
            dragon += cnt * (m - i);
        }
        else if (i > m) {
            tiger += cnt * (i - m);
        }
    }

    return dragon - tiger;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> c[i];
    }
    cin >> m >> p1 >> s1 >> s2;

    int ans = 1;
    long long best_abs = -1;

    // 直接枚举最后把 s2 放到哪个兵营,并重新统计双方气势。
    for (int p2 = 1; p2 <= n; p2++) {
        long long diff = calc_diff_after_choose(p2);
        long long cur_abs = llabs(diff);
        if (best_abs == -1 || cur_abs < best_abs ||
            (cur_abs == best_abs && p2 < ans)) {
            best_abs = cur_abs;
            ans = p2;
        }
    }

    cout << ans << '\n';

    return 0;
}

但正式做法没必要每次都从头重算。

先把第一次加入 s1 之后的双方气势差记成:

diff = dragon - tiger

接下来枚举 p2 时:

  • 如果 p2 < m,说明 s2 加到龙方,diff 会增加 s2 × (m - p2)
  • 如果 p2 > m,说明 s2 加到虎方,diff 会减少 s2 × (p2 - m)
  • 如果 p2 = m,双方气势都不变

所以每个位置的结果都能在 O(1)O(1) 算出来。

这样只要:

  1. 先算出第一次事件后的 diff
  2. 枚举所有 p2
  3. 计算新的差值绝对值
  4. 取最小值;若相同取更小的兵营编号

就可以了。

注意:官方题面要求如果有多个最优答案,输出编号最小的兵营。

代码

cpp
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 100005;

int n, m, p1, p2_ans;
long long s1, s2;
long long c[MAXN];

long long calc_initial_diff() {
    long long dragon = 0;
    long long tiger = 0;

    for (int i = 1; i <= n; i++) {
        if (i < m) {
            dragon += c[i] * (m - i);
        }
        else if (i > m) {
            tiger += c[i] * (i - m);
        }
    }

    if (p1 < m) {
        dragon += s1 * (m - p1);
    }
    else if (p1 > m) {
        tiger += s1 * (p1 - m);
    }

    return dragon - tiger;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> c[i];
    }
    cin >> m >> p1 >> s1 >> s2;

    long long diff = calc_initial_diff();
    long long best_abs = -1;

    p2_ans = 1;
    for (int p2 = 1; p2 <= n; p2++) {
        long long new_diff = diff;

        if (p2 < m) {
            new_diff += s2 * (m - p2);
        }
        else if (p2 > m) {
            new_diff -= s2 * (p2 - m);
        }

        long long cur_abs = llabs(new_diff);
        if (best_abs == -1 || cur_abs < best_abs ||
            (cur_abs == best_abs && p2 < p2_ans)) {
            best_abs = cur_abs;
            p2_ans = p2;
        }
    }

    cout << p2_ans << '\n';

    return 0;
}

复杂度

先统计一次原始气势,再枚举所有 p2

时间复杂度是 O(n)O(n),空间复杂度是 O(1)O(1)

总结

这题的关键是把“第二次投放后的总气势”看成对初始差值 diff 的一次简单修正。

一旦把这个关系写出来,枚举位置就够了。