P14361 [CSP-S 2025] 社团招新 / club
当时考试的时候五十分钟写完的,不像个绿题,降黄还差不多。
虽然说反悔贪心的板子是绿,但是我考试想这个思路的时候,没有意识到这是个反悔贪心。
我是怎么想的呢?假设你就是人类小 L,你作为人类会怎么分?肯定是先让大家选最满意的社团了。如果此时哪个社团都没有超人数,那么直接输出答案就好了。
(以下称这部分的答案是 \(sum\),即 \(n\) 个人最大满意度之和)
如果有超人数的社团呢?首先,任意时刻都至多只有一个社团会超人数(否则人数超过 \(n\)),其次,我们还是用人类的视角去看这个问题。
(以下将三个社团编号为 \(0,1,2\),假设当前超了人数的社团编号为 \(id\),报名它的人数为 \(num_{id}\))
正常情况下超了人数,会长出面协调的时候,肯定会尽可能小地调动人,而且只会调整报了 \(id\) 社团的人,让他们去另外的社团。
我们可以把这个过程看作调人后,\(sum\) 不断减当前这个人 最满意社团满意度 与 当前社团满意度 的差值。
那我们为了收益最大,\(sum\) 减的值最小,一来一定会把他们调整到第二满意的社团,二来我们会不断选择 当前报了 \(id\) 社团 且 最满意社团满意度 与 第二满意社团满意度 差值最小 的人,这样能保证 \(sum\) 减的数字尽可能小。
这就是我们以人类视角想出来的正解。那有的人就要问了:我这样调整社团的时候,没有保证其他两个社团不超人数限制。万一我调整完 \(id\) 社团后,又有一个社团人数超限了呢?
事实上并不会出现这种情况。因为我刚才的过程调整了尽可能少的人,也就是调整完后恰有 \(num_{id}=n/2\)。如果说此时有另一个社团人数超限,那么总人数就会大于 \(n\),矛盾。故我们执行此贪心策略时不用担心其他社团人数超限的问题。
代码:
P14361
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int x=0,f=1;char c=getchar();
while(c<48){
if(c=='-') f=-1;
c=getchar();
}
while(c>47) x=(x<<1)+(x<<3)+(c^48),c=getchar();
return x*f;
}
const int N=1e5+5;
int T,n,a[3][N],cha[3][2][N],sum,num[3],cho[N];
//cha[id][0/1][i]:0/1分别表示第i个人对id社团的满意度与另两个社团的满意度之差
//比如cha[1][0][i]表示第i个人对第1个社团的满意度与对第0个社团的满意度之差
//cha[1][1][i]表示 第i个人对第1个社团的满意度与对第2个社团的满意度之差
//num[id]、sum:同题解
//cho[i]:i调整前选了哪个社团
priority_queue<int,vector<int>,greater<int> > q;
//q是小根堆,用来处理当前最小的 最满意社团 与 次满意社团 满意度之差
inline void INIT(){
num[0]=num[1]=num[2]=0;sum=0;
for(int i=1;i<=n;i++){
cha[0][0][i]=cha[0][1][i]=cha[1][0][i]=0;
cha[1][1][i]=cha[2][0][i]=cha[2][1][i]=0;
cho[i]=-1;
}
while(!q.empty()) q.pop();
}
inline void cl(int id){//处理编号为id的人
if(a[0][id]>=a[1][id]&&a[0][id]>=a[2][id]){//选择0社团
num[0]++;cho[id]=0;
}
else if(a[1][id]>=a[0][id]&&a[1][id]>=a[2][id]){//选择1社团
num[1]++;cho[id]=1;
}
else if(a[2][id]>=a[0][id]&&a[2][id]>=a[1][id]){//选择2社团
num[2]++;cho[id]=2;
}
cha[0][0][id]=a[0][id]-a[1][id];cha[0][1][id]=a[0][id]-a[2][id];
cha[1][0][id]=a[1][id]-a[0][id];cha[1][1][id]=a[1][id]-a[2][id];
cha[2][0][id]=a[2][id]-a[0][id];cha[2][1][id]=a[2][id]-a[1][id];
}
inline void solve(int id){
//solve(id):当前编号为id的社团人数超了,我们该怎么处理
int numb=0;
for(int i=1;i<=n;i++){
if(cho[i]==id){
q.push(min(cha[id][0][i],cha[id][1][i]));
numb++;
}
}
while(numb>n/2){
int s=q.top();q.pop();
sum-=s;
numb--;
}
}
signed main(){
T=read();
while(T--){
n=read();
INIT();//多测不清空,亲人两行泪
//sum同题解
for(int i=1;i<=n;i++){
a[0][i]=read(),a[1][i]=read(),a[2][i]=read();
sum+=max(max(a[0][i],a[1][i]),a[2][i]);
cl(i);
}
if(num[0]>n/2){
solve(0);
}
else if(num[1]>n/2){
solve(1);
}
else if(num[2]>n/2){
solve(2);
}
printf("%lld\n",sum);
}
return 0;
}

浙公网安备 33010602011771号