学生分组

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

先判断总人数是否落在可行范围内,再统计总缺口和总超额,答案就是两者的较大值。

OJ: luogu

题目 ID: P1109

难度:普及/提高-

标签:思维贪心

日期: 2026-06-19 00:43

题意

给定 n 个组当前的人数,以及每组允许的人数范围 [L,R]
每次操作可以把某一组里的一个学生移动到另一组。
要求最少经过多少次操作,才能让所有组的人数都落在 [L,R] 中;如果做不到,输出 -1

思路

先看一个可以直接验证想法的朴素解:

枚举每个组最终应该有多少人,只要这些目标人数都在 [L,R] 中且总人数不变,就能算出实现这个目标分配至少要几次转移。

cpp
// brute.cpp:小数据暴力解,用来帮助理解题意并辅助对拍。
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 10;
const int INF = 1e9;

int n;
int a[MAXN];
int l, r;
int best;

void dfs(int idx, int left_sum, int moved_out) {
    if (idx > n) {
        if (left_sum == 0) {
            best = min(best, moved_out);
        }
        return;
    }

    int remain = n - idx;
    for (int v = l; v <= r; v++) {
        int rest = left_sum - v;
        if (rest < 0) {
            continue;
        }
        if (rest < remain * l || rest > remain * r) {
            continue;
        }
        dfs(idx + 1, rest, moved_out + max(0, a[idx] - v));
    }
}

void solve() {
    int sum = 0;
    for (int i = 1; i <= n; i++) {
        sum += a[i];
    }

    if (sum < n * l || sum > n * r) {
        cout << -1 << '\n';
        return;
    }

    best = INF;
    dfs(1, sum, 0);
    cout << best << '\n';
}

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

    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    cin >> l >> r;

    solve();

    return 0;
}

这个办法可以帮助理解,但没有必要真的枚举最终分配。

我们只需要关心两种组:

  • 人数小于 L 的组:它们至少需要补进一些学生
  • 人数大于 R 的组:它们至少需要挪出一些学生

记:

  • need_in:所有缺人组总共至少缺多少人
  • need_out:所有超额组总共至少要送出多少人

如果总人数本身都不在 [nL, nR] 内,那一定无解,直接输出 -1

如果总人数合法,那么一次操作可以同时完成两件事:

  • 某组少 1 人
  • 另一组多 1 人

因此最少操作次数一定不少于 need_inneed_out,同时也总能在 max(need_in, need_out) 次内完成调整。
所以答案就是:

max(need_in, need_out)

代码

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

const int MAXN = 55;

int n;
int a[MAXN];
int l, r;

void solve() {
    int sum = 0;
    int need_in = 0;   // 不足下界的组,总共至少需要补进多少人
    int need_out = 0;  // 超过上界的组,总共至少需要挪出多少人

    for (int i = 1; i <= n; i++) {
        sum += a[i];
        if (a[i] < l) {
            need_in += l - a[i];
        }
        else if (a[i] > r) {
            need_out += a[i] - r;
        }
    }

    if (sum < n * l || sum > n * r) {
        cout << -1 << '\n';
        return;
    }

    cout << max(need_in, need_out) << '\n';
}

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

    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    cin >> l >> r;

    solve();

    return 0;
}

复杂度

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

总结

这题的关键不在模拟移动过程,而在于直接统计“总缺口”和“总超额”。

只要总人数可行,答案就可以直接写成 max(缺口, 超额)