#include<bits/stdc++.h>//题意就是说找一些人,且刚好能凑成k队(所以需要考虑最坏的情况,就是都找一些不能凑成一队的人)(题意表述不太清晰) #define int long long//思路:先拿走少于3的队伍的人数 usingnamespace std;//再在大于3个人的队伍中拿走尽可能多的人数,剩下的人数再存起来;比如有两个队(6,4),如果需要拿走两个队,就在6中拿走5个人,4中拿走2个人,意思就是尽可能拿走一个队伍再余2个人 constint N=2e5+10;//最后如果队伍数还凑不够的话,再从剩下的人数中拿,每拿一个人队伍数就会加一 int a[N]; voidsolve() { int n,k,m=0; cin>>n>>k; map<int,int>mp,book;//因为x最大1e9,所以不能用数组存 for(int i=1;i<=n;i++) { int x,y; cin>>x>>y; if(!book[x])//防止职业重复存 { book[x]=1; a[++m]=x;//用数组记录下职业 } mp[x]+=y; } int cnt=0; for(int i=1;i<=m;i++) { if(mp[a[i]]>=3) cnt+=mp[a[i]]/3; } if(cnt<k)//算算够不够凑成k队 { cout<<-1<<'\n'; return; } priority_queue<int,vector<int>>q,qq,qqq;//从大到小排序 for(int i=1;i<=m;i++) { q.push(mp[a[i]]); } int ans=0; while(q.size()) { auto x=q.top(); q.pop(); if(x>=3)//将比3大的存起来进行计算 qq.push(x); else ans+=x;//比3小的直接加起来,因为他们不可能组成一个队(题意说3个相同的职业才能组成一个队) } while(qq.size()) { auto x=qq.top(); qq.pop(); int kk=(x+1)/3-1;//这里意思是比如说一个队6个人(或7个人,8个人),想只凑成一队,最多可以拿5个人;x+1是考虑到一个队5个人,要凑成一队可以直接拿走5个人,如果不加一,就会拿走2个人 int t=min(kk,k-1);//这里k-1意思是空一个队伍,方便以后还有大于3人的队伍取不到(剩下的队伍都还能取2个,增添一下人数),要不后面k=0直接退出循环了 // int res=t*3; k-=t;//队伍数减去t ans+=(t*3+2);//尽量每次多取两个,人数最大化 x-=(t*3+2); if(x) qqq.push(x);//把剩下的人单独拿出来 if(k==0)break; } while(qqq.size())//这里放着的都是从大于3个的中拿剩下的,在这里随便拿一个,就会使队伍数量加一 { auto x=qqq.top(); qqq.pop(); if(k==0) break; // int t=min(k,x); // cout<<k<<" "<<x<<" "<<t<<'\n'; ans++;//假如本来有4个,存到这里时变成了2个,但是你只能取一个,不能2个都取走(2个都取走会不符合题意)(下面样例自己带入一下可能会清楚一些) k--; } cout<<ans<<'\n'; /* 比如样例: 3 3 1 4 2 4 3 4 */ return; } signedmain() { ios::sync_with_stdio(0);//快输 cin.tie(0),cout.tie(0); int T=1; cin>>T; while(T--) solve(); return0; }
你的代码核心思想是 尽量选择合适的士兵,使其能刚好组成 k 个纯职业小组。我们从 最坏的情况 出发,考虑如何选择士兵,确保 k 个小组可以被组成。下面详细解析代码的逻辑和思路:
1. 题目分析
每个小队由相同职业的士兵组成。
目标是至少选 k 个士兵队伍,每个队伍至少由 3 个相同职业的士兵 组成。
最坏情况 下,可能会遇到一些士兵无法凑满 3 人的小组,这会影响队伍数量。
我们需要找到 最少的士兵人数 来确保 k 个小组可以组建成功。
2. 代码逻辑分析
(1) 读取输入 & 统计每种职业的士兵数量
1 2 3 4 5 6 7 8 9 10 11 12
int n, k, m = 0; cin >> n >> k; map<int, int> mp, book; for (int i = 1; i <= n; i++) { int x, y; cin >> x >> y; // x: 职业,y: 该职业的士兵数量 if (!book[x]) { // 防止重复存储相同职业 book[x] = 1; a[++m] = x; // 记录职业种类 } mp[x] += y; // 统计每个职业的士兵总数 }
解析:
mp[x] 记录 职业 x 的士兵总数。
book[x] 避免 重复统计相同职业,因为可能存在多个小队有相同职业。
a[] 记录 所有不同职业的编号。
(2) 计算能完整组成 k 组的职业数
1 2 3 4 5 6 7 8 9
int cnt = 0; for (int i = 1; i <= m; i++) { if (mp[a[i]] >= 3) cnt += mp[a[i]] / 3; // 每 3 人可以组成 1 队 } if (cnt < k) { // 如果连 k 组都凑不出来,直接输出 -1 cout << -1 << '\n'; return; }
解析:
计算所有职业 能组成多少完整的小队,即 cnt。
如果 cnt < k,表示无论如何 都无法组成 k 组,直接输出 -1。
(3) 按照职业的士兵数量进行分类
1 2 3 4
priority_queue<int, vector<int>> q, qq, qqq; for (int i = 1; i <= m; i++) { q.push(mp[a[i]]); // 大顶堆,优先选择士兵最多的职业 }
解析:
使用 优先队列(大顶堆),保证 尽可能先从士兵多的职业选取。
(4) 处理士兵数量小于 3 的情况
1 2 3 4 5 6 7 8 9
int ans = 0; while (q.size()) { auto x = q.top(); q.pop(); if (x >= 3) qq.push(x); // 将数量 ≥3 的放入新的堆 else ans += x; // 数量小于 3 的,直接累加到最终答案 }
解析:
数量小于 3 的士兵 不能组成小组,所以他们的数量直接累加。
数量大于等于 3 的士兵 可以尝试组成小组,存入 qq。
(5) 处理士兵数量 >=3 的情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14
while (qq.size()) { auto x = qq.top(); qq.pop(); int kk = (x + 1) / 3 - 1; // 计算最多可以拿走多少人 int t = min(kk, k - 1); // 不能超过剩余需要的小组数(k-1) k -= t; // 剩余需要的小组数减少 t ans += (t * 3 + 2); // 每次尽量多取 3 人,同时多加 2 人 x -= (t * 3 + 2); if (x) qqq.push(x); // 还剩下的人存入另一个队列 if (k == 0) break; }
解析:
优先从大于 3 人的职业中拿走尽可能多的士兵,确保先组 k-1 个队伍。
kk = (x+1)/3 -1:
计算该职业可以 优先组出几个队伍,留下 2 个士兵。
t = min(kk, k-1):
不能超过剩余需要的小组数 (k-1),否则有浪费。
ans += (t * 3 + 2):
选 t 组小队,每组 3 人,同时多选 2 人。
剩余士兵数量 x 仍然大于 0,放入 qqq 队列,等待后续分配。
(6) 处理剩余士兵
1 2 3 4 5 6 7
while (qqq.size()) { auto x = qqq.top(); qqq.pop(); if (k == 0) break; ans++; // 每次选一个士兵 k--; }