徐老师的团队活动

【题目来源】

瑞学堂:徐老师的团队活动

【题目描述】

徐老师最近准备给同学们组织团队活动——猫捉老鼠,但是选多少人当猫,选哪些人成了问题

每个同学的体力,热衷程度均不同,有的人希望当猫去抓人,有的人希望自己团队的人数多一些...

于是徐老师统计了每个人的如果被选作当老鼠逃跑时,他希望的团队人数 \(a_i\)

如果第 \(i\) 位同学被选中当老鼠,那么他希望老鼠团队的人数是超过 \(a_i\) 的(这样自己不容易被抓)
如果第 \(i\) 位同学被选中当猫,那么他希望老鼠团队的人数是少于 \(a_i\) 的(这样作为猫的一方自己更容易获得胜利)

现在徐老师想知道,有多少选择方案可以满足每个同学的希望呢(每个人必须要么是老鼠要么是猫)?

P.S. 这里注意,允许所有同学都当老鼠或者都当猫,此时可以由许老师来担任另一个身份(并且许老师不算在团队人数内)

【输入】

输入第一行包含一个整数 \(n\) 表示一共有多少同学
输入第二行包含 \(n\) 个整数,分别表示第 \(i\) 个人的希望团队人数 \(a_i\)

【输出】

输出一个整数表示有多少种选择方案

【输入样例】

4
0 3 3 2

【输出样例】

2

【核心思想】

  1. 问题分析:给定 \(n\) 位同学,每人有一个希望值 \(a_i\)。若选为老鼠,要求老鼠团队人数 \(x > a_i\);若选为猫,要求老鼠团队人数 \(x < a_i\)。求所有满足每个人希望的选择方案数。这是一个排序后枚举 + 贪心验证问题,关键在于发现:最优策略是将 \(a_i\) 排序后,选择前 \(x\) 小的人当老鼠,后 \(n-x\) 大的人当猫,只需验证边界条件。

  2. 算法选择

    • 排序(Sort):将 \(a_i\) 从小到大排序,使得"前 \(x\) 位当老鼠、后 \(n-x\) 位当猫"的贪心策略成立
    • 贪心策略:排序后,\(a_i\) 越小的人越适合当老鼠(因为老鼠要求 \(x > a_i\)\(a_i\) 小更容易满足),\(a_i\) 越大的人越适合当猫
    • 枚举验证:枚举老鼠团队人数 \(x\)\(0\)\(n\),利用排序后的单调性只需检查边界元素
  3. 关键步骤

    • 读取与排序:读入 \(n\) 和数组 \(a[1..n]\),将 \(a\) 从小到大排序
    • 枚举 \(x\)(老鼠团队人数,从 \(0\)\(n\)):
      • 边界情况\(x = 0\)(全猫),检查 \(a_1 > 0\)\(x = n\)(全鼠),检查 \(a_n < n\)
      • 一般情况\(0 < x < n\)):
        • \(x\) 位(老鼠):最大希望值为 \(a_x\),需满足 \(a_x < x\)(老鼠人数超过其希望)
        • \(n-x\) 位(猫):最小希望值为 \(a_{x+1}\),需满足 \(a_{x+1} > x\)(老鼠人数少于其希望)
        • 即验证:a[x] < x && a[x+1] > x
    • 统计答案:满足条件的 \(x\) 个数即为方案数
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n \log n)\),排序 \(O(n \log n)\),枚举验证 \(O(n)\)
    • 空间复杂度:\(O(n)\),存储数组 \(a\)
  5. 贪心 + 排序的核心思想

    • 排序创造单调性:排序后,前 \(x\) 位必然是最"容易满足当老鼠条件"的人(\(a_i\) 小),后 \(n-x\) 位是最"容易满足当猫条件"的人(\(a_i\) 大),贪心选择最优
    • 边界压缩验证:排序后只需检查分界处的两个元素 \(a_x\)\(a_{x+1}\),无需遍历所有人,利用单调性将 \(O(n)\) 验证降为 \(O(1)\)
    • 枚举与贪心的结合:枚举所有可能的团队人数 \(x\),但利用排序后的结构使每次验证极高效
    • 全选边界处理\(x=0\)\(x=n\) 时分别只需检查最值,体现贪心策略的完备性
    • 适用于"将集合划分为两部分,每部分有独立约束,且约束具有单调性"的划分计数问题

【算法标签】

贪心

【代码详解】

#include <bits/stdc++.h>
using namespace std;
const int N = 100005;    // 定义数组最大容量为100005
int n, ans;              // n为同学数量,ans记录满足条件的选择方案数
int a[N];                // a[i]表示第i位同学希望的团队人数

// 检查选择x位同学当老鼠、n-x位同学当猫是否满足所有人的希望
// 策略:将a数组排序后,选择前x位同学当老鼠(希望团队人数>a_i,即a_i<x),后n-x位当猫(希望团队人数<a_i,即a_i>x)
bool check(int x)
{
    if (x == 0)          // 所有同学都当猫(老鼠团队人数为0)
    {
        return a[1] > 0;  // 所有人都希望老鼠团队人数<a_i,即a_i>0,只需检查最小的a[1]
    }
    else if (x == n)     // 所有同学都当老鼠(老鼠团队人数为n)
    {
        return a[n] < n;  // 所有人都希望老鼠团队人数>a_i,即a_i<n,只需检查最大的a[n]
    }
    else                 // x位同学当老鼠,n-x位同学当猫
    {
        // 前x位当老鼠:需要a_i < x(老鼠团队人数x > a_i)
        // 后n-x位当猫:需要a_i > x(老鼠团队人数x < a_i)
        // 排序后只需检查边界:第x位(最大的老鼠)和第x+1位(最小的猫)
        return (a[x] < x) && (a[x + 1] > x);
    }
}

int main()
{
    cin >> n;            // 读入同学数量n
    for (int i = 1; i <= n; i++)  // 读入每位同学希望的团队人数
        cin >> a[i];
    sort(a + 1, a + n + 1);  // 将希望人数从小到大排序,便于二分/枚举边界

    // 枚举老鼠团队人数x从0到n,检查每种方案是否可行
    for (int i = 0; i <= n; i++)
    {
        if (check(i))    // 如果x位老鼠的方案满足所有人的希望
            ans++;       // 方案数加1
    }
    cout << ans << endl;  // 输出满足条件的选择方案总数
    return 0;
}

【运行结果】

4
0 3 3 2
2
posted @ 2026-08-06 08:44  团爸讲算法  阅读(6)  评论(0)    收藏  举报