涨薪

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

题目描述

公司有 nn 人,第 ii 人初始工资 aia_i。每年选 xx 人绩效 A(工资 ×3\times 3)、yy 人绩效 B(×2\times 2),其余绩效 C(工资不变),连续两年绩效 C 会被开除,且不招新人。输入第一行 n,m,x,yn,m,x,y,第二行 nn 个 aia_i;求 mm 年后在职员工工资总和的最大值,对 109+710^9+7 取模。1⩽n⩽1051\leqslant n\leqslant 10^5,1⩽m⩽1091\leqslant m\leqslant 10^9,1⩽ai⩽1051\leqslant a_i\leqslant 10^5。

样例:2 1 1 1 / 5 3 输出 21;2 2 0 0 / 5 2 输出 0。

思路

工资降序排序后,m⩾2m\geqslant 2 时让最小的 k=n−x−yk=n-x-y 人前两年连续拿 C 被开除,此后在职者恰剩 x+yx+y 人年年涨薪、涨薪名额零浪费,故答案是 3mSx+2m(Sx+y−Sx)3^m S_x+2^m(S_{x+y}-S_x),其中 SjS_j 为前 jj 大工资之和。m=1m=1 时无人会被开除,拿 C 的人照领原工资,需再加 Sn−Sx+yS_n-S_{x+y}。

参考代码

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-10-02 18:07
# update_at: 2026-10-02 18:07

import sys
from itertools import accumulate

MOD = 10**9 + 7  # 答案模数


def solve() -> None:
    data = iter(map(int, sys.stdin.buffer.read().split()))
    n, m, x, y = next(data), next(data), next(data), next(data)

    # 工资降序排列;pre[j] = 前 j 大的工资之和
    salary = sorted((next(data) for _ in range(n)), reverse=True)
    pre = [0, *accumulate(salary)]

    if m == 1:
        # 只有一年:无人会连续两年拿 C,开除不会发生,拿 C 者照领原工资
        ans = 3 * pre[x] + 2 * (pre[x + y] - pre[x]) + (pre[n] - pre[x + y])
    else:
        # 两年起:工资最小的 k = n-x-y 人在第 1、2 年连续拿 C 被开除,
        # 幸存者恰好 x + y 人,此后年年涨薪,名额零浪费
        three, two = pow(3, m, MOD), pow(2, m, MOD)
        ans = three * pre[x] + two * (pre[x + y] - pre[x])

    print(ans % MOD)


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-04 21:36
 * update_at: 2026-10-04 21:36
 */
// main.cpp:工资降序后,m>=2 时把最小的 n-x-y 人连续两年拿 C 开除,
// 幸存者恰 x+y 人年年涨薪,答案为 3^m*S_x + 2^m*(S_{x+y}-S_x);m=1 无人被开除需补上拿 C 者的原工资。
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

const int MAXN = 100005;   // n 的上界
const ll MOD = 1000000007; // 答案模数

ll salary[MAXN]; // salary[i] 表示工资降序后第 i 大的初始工资(下标从 1 开始)
ll pre[MAXN];    // pre[j] 表示工资最高的前 j 人的工资之和

// 快速幂:返回 base^exp mod MOD,用于计算 m 年的总倍数 3^m、2^m
ll power(ll base, ll exp) {
    ll result = 1;
    base %= MOD;
    while (exp > 0) {
        if (exp & 1) {
            result = result * base % MOD;
        }
        base = base * base % MOD;
        exp >>= 1;
    }
    return result;
}

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

    ll n, m, x, y;
    cin >> n >> m >> x >> y;
    for (ll i = 1; i <= n; i++) {
        cin >> salary[i];
    }

    // 涨薪名额总是给当前工资最高的人最划算,故按工资从大到小排序
    sort(salary + 1, salary + n + 1, greater<ll>());
    for (ll i = 1; i <= n; i++) {
        pre[i] = pre[i - 1] + salary[i];
    }

    ll ans;
    if (m == 1) {
        // 只有一年:不可能连续两年拿 C,无人被开除,拿 C 的人照领原工资
        ans = 3 * (pre[x] % MOD) % MOD;
        ans = (ans + 2 * ((pre[x + y] - pre[x]) % MOD)) % MOD;
        ans = (ans + (pre[n] - pre[x + y]) % MOD) % MOD;
    } else {
        // 让最小的 k = n - x - y 人前两年连续拿 C 被开除,
        // 此后在职者恰好 x + y 人年年涨薪:前 x 大总倍数 3^m,接下来 y 大总倍数 2^m
        ll three = power(3, m);
        ll two = power(2, m);
        ans = three * (pre[x] % MOD) % MOD;
        ans = (ans + two * ((pre[x + y] - pre[x]) % MOD)) % MOD;
    }

    cout << ans % MOD << '\n';
    return 0;
}