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;
} 
posted @ 2025-11-03 07:48  qwqSW  阅读(46)  评论(0)    收藏  举报