徐老师的团队活动
【题目来源】
瑞学堂:徐老师的团队活动
【题目描述】
徐老师最近准备给同学们组织团队活动——猫捉老鼠,但是选多少人当猫,选哪些人成了问题
每个同学的体力,热衷程度均不同,有的人希望当猫去抓人,有的人希望自己团队的人数多一些...
于是徐老师统计了每个人的如果被选作当老鼠逃跑时,他希望的团队人数 \(a_i\)
如果第 \(i\) 位同学被选中当老鼠,那么他希望老鼠团队的人数是超过 \(a_i\) 的(这样自己不容易被抓)
如果第 \(i\) 位同学被选中当猫,那么他希望老鼠团队的人数是少于 \(a_i\) 的(这样作为猫的一方自己更容易获得胜利)
现在徐老师想知道,有多少选择方案可以满足每个同学的希望呢(每个人必须要么是老鼠要么是猫)?
P.S. 这里注意,允许所有同学都当老鼠或者都当猫,此时可以由许老师来担任另一个身份(并且许老师不算在团队人数内)
【输入】
输入第一行包含一个整数 \(n\) 表示一共有多少同学
输入第二行包含 \(n\) 个整数,分别表示第 \(i\) 个人的希望团队人数 \(a_i\)
【输出】
输出一个整数表示有多少种选择方案
【输入样例】
4
0 3 3 2
【输出样例】
2
【核心思想】
-
问题分析:给定 \(n\) 位同学,每人有一个希望值 \(a_i\)。若选为老鼠,要求老鼠团队人数 \(x > a_i\);若选为猫,要求老鼠团队人数 \(x < a_i\)。求所有满足每个人希望的选择方案数。这是一个排序后枚举 + 贪心验证问题,关键在于发现:最优策略是将 \(a_i\) 排序后,选择前 \(x\) 小的人当老鼠,后 \(n-x\) 大的人当猫,只需验证边界条件。
-
算法选择:
- 排序(Sort):将 \(a_i\) 从小到大排序,使得"前 \(x\) 位当老鼠、后 \(n-x\) 位当猫"的贪心策略成立
- 贪心策略:排序后,\(a_i\) 越小的人越适合当老鼠(因为老鼠要求 \(x > a_i\),\(a_i\) 小更容易满足),\(a_i\) 越大的人越适合当猫
- 枚举验证:枚举老鼠团队人数 \(x\) 从 \(0\) 到 \(n\),利用排序后的单调性只需检查边界元素
-
关键步骤:
- 读取与排序:读入 \(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\) 个数即为方案数
-
时间/空间复杂度:
- 时间复杂度:\(O(n \log n)\),排序 \(O(n \log n)\),枚举验证 \(O(n)\)
- 空间复杂度:\(O(n)\),存储数组 \(a\)
-
贪心 + 排序的核心思想:
- 排序创造单调性:排序后,前 \(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
浙公网安备 33010602011771号