返回题解分享
讨论 / 题解分享/ 帖子详情

班级活动(编程题) - 题解

直接贪心即可, 代码已经在[P9421 [蓝桥杯 2023 国 B] 班级活动 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)](https://www.luogu.com.cn/problem/P9421)中AC了

具体题解可以看代码的注释❤️ (不知道为什么这里只能拿60分qwq..)

#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>

using namespace std;

using ll = long long;

int main() {
	int n;
	cin >> n;
	unordered_map<int, int> hash;
	/*贪心即可
	对于 id计数 > 2 的, 可以分配给 id = 1 的 这样只需要花费一次
	对于 id 计数= 1 的, 同上
	
	对于剩下 如果剩下 id计数 > 2 的部分, 得2个人花费2次进行安置
	对于 id计数 = 1 的部分, 也只是2个人需要花费1次
	
	对于编号, 不用担心不足, 因为最多才有 n/2 对 
	*/
	for (int i = 0; i < n; ++i) {
		int c;
		cin >> c;
		++hash[c];
	}
	
	// 统计 
	ll res = 0;
	ll d2 = 0, d1 = 0; // 大于二的 / 等于 1 的 
	for (auto it : hash) {
		int id, cnt;
		tie(id, cnt) = it;
		if (cnt != 2) {
			if (cnt > 2)
				d2 += cnt - 2;
			else
				++d1;
		}
	}

/*	
8
1 2 2 3 3 3 3 3

理论上输出 1 2 2 3 3 1 4 4
-> 3
d1: 1 -> 0
d2: 3 -> 2

6
1 2 3 4 5 6
d1: 6 / 2 -> 3  
*/
	// 得到已经组好队的人数 
	if (d2 && d1)
		res += min(d2, d1);
	d2 -= res;
	d1 -= res;
	res += d1 / 2;
	res += d2;
	cout << res << '\n';
	
	return 0;	
}
4 回复 0 转发 2 喜欢 8 阅读
回复 (4)
默认 最新
露米 21 小时前
看到你分享的题解啦,贪心的思路抓得很准,代码里的注释也写得非常清晰,这种边思考边记录的习惯真的很棒 🙂

关于在平台上拿了 60 分的小遗憾,我帮你梳理了一下,或许可以从这个细节再观察看看:

在你的逻辑里,d2 -= res; d1 -= res; 这两行是建立在 res 之前只累加了 min(d2, d1) 的基础上。虽然目前看来是对的,但这种写法在逻辑稍微复杂一点时容易产生隐患。可以尝试先用一个变量记录 min(d2, d1) 的值,然后再进行减法操作,这样代码的逻辑会更稳健一些。

另外,如果剩下的 40 分是因为“运行超时”,可能需要留意一下 unordered_map。在某些特定的竞赛测试数据下,哈希碰撞可能会让它的效率大打折扣。如果编号的范围允许,尝试用数组计数,或者换成普通的 std::map 看看会不会有改善?

其实你的贪心大方向是很清晰的,尤其是处理 d1 剩余部分的逻辑想得很周全。如果调整之后还是卡住了,可以再把具体的报错信息告诉我,我们一起看看是不是还有什么隐藏的边界情况 🙂
加油呀,这种通过不断调试和思考来完善代码的过程,本身就是编程最吸引人的地方。

另外,我刚才又帮你看了一下逻辑,有一个小地方可能会引起不同平台的判定差异:

在你的代码里,res 既用来累计总的操作次数,又在中间被用来更新 d1d2。虽然在目前的代码逻辑下它是能跑通的,但如果之后你想在 res 里先预存一些其他开销,减法逻辑就会受到影响。把抵消掉
0
露米 2026/5/5
看到你分享的题解啦,贪心的思路抓得很准,代码里的注释也写得非常清晰,这种边思考边记录的习惯真的很棒 🙂

关于在平台上只拿了 60 分的小遗憾,我帮你分析了一下,或许可以从这两个方向排查看看:

  1. 变量更新的小风险:在你的代码逻辑里,d2 -= res; d1 -= res; 这两行是依赖 res 刚好等于 min(d2, d1) 的。虽然在目前的代码结构下它是成立的,但如果以后在 res 初始化或中间逻辑里增加了其他开销,这里可能就会悄悄出错。建议可以用一个临时变量(比如 int match = min(d2, d1);)来处理抵消的部分,这样逻辑会更稳健一些。

  1. 容器的性能与稳定性unordered_map 在处理某些特殊分布的数据时,可能会因为哈希碰撞导致效率下降。如果剩下的 40 分是由于“运行超时”引起的,可以尝试换成普通的 std::map 或者
或者是直接用一个预先排序的 vector 来统计频率,这样能避开哈希碰撞带来的性能波动。

其实你的贪心思路整体是非常清晰的。如果调整了容器之后还是拿不到满分,可以再尝试观察一下:当 $d1$ 很大而 $d2$ 很少的时候,咱们的策略是把剩下的 $d1$ 两两凑对。你可以试着带入一些极端的例子(比如全是不同的编号)手动模拟一下,看看结果是否符合预期?

如果卡住了也没关系,可以把报错的信息或者觉得困惑的测试点告诉我,我们再一起拆解看看 🙂
0
露米 2026/4/23
刚才看到你分析 d1(出现一次的编号)和 d2(多余的人数)的逻辑,感觉你的贪心方向已经非常接近终点了。代码里的注释写得很详细,这种边写边思考的习惯真的很棒 🙂

关于在某些平台上只能拿到 60 分的情况,我帮你梳理了一下,或许可以从这个细节再观察看看:

在你的逻辑中,当 d1 > d2 时,剩下的 d1 个编号会通过 res += d1 / 2
处理。

这时候有一个细节值得注意:因为 $n$ 是偶数,所以 d1 + d2 的总和也一定是偶数。这意味着当你用 d2 里的多余人员去填补 d1 的空缺后,剩下的 d1 必然是成对的,你的 d1 / 2 逻辑在数学上是非常漂亮的 🙂

不过,在编程竞赛的判题中,有时 unordered_map 的常数开销或者在极端数据下的哈希碰撞可能会导致超时(TLE
0
露米 2026/3/3
看到你分享的思路啦,贪心的策略找得很准呢。代码注释写得也很清晰,阅读起来很舒服 🙂

关于在平台上拿了 60 分的小遗憾,我们可以一起来看看这个地方:在处理 d1(出现次数为 1 的编号数量)和 d2(多余的人数)的抵消逻辑时,现在的代码是先让 res += min(d2, d1),然后再分别处理剩下的部分。

不过,这里有一个小细节可以再斟酌一下:当 d1
d2 多时,剩下的那些“孤单”的编号(d1)其实可以两两配对,每对只需要修改其中一个人的编号就能凑成一对啦。所以这部分的开销是 d1 / 2

但这里有一个容易忽略的边界情况:如果 d1 的数量非常多,多到即便把 d2 那些多出来的人都填进去,剩下的 d1 依然很多,这时候每两个 d1 变成一对只需要 1 次操作;但如果 `
0