【洛谷6831】[IOI2020] 嘉年华奖券(贪心+堆套路)

点此看题面

  • (n)种颜色的奖券((n)是偶数),每种颜色有(m)张,每张奖券上都写着一个数。
  • 会进行(k)轮游戏,每轮你可以从每种颜色中各选出一张未被选过的奖券,然后举办方会给出一个数来最小化这些奖券上的数与给出数差的绝对值之和,这个和就是你这一轮的收益。
  • 求构造一种方案,最大化总收益。
  • (nle1500,kle mle1500)

举办方的策略

根据初中知识,对于你选出的这(n)个数,举办方一定会选择它们的中位数来最小化差的绝对值之和。

然后我们就会发现(n)是偶数这个条件有什么用了。

这样一来这个和实际上就相当于较大的(frac n2)个数与较小的(frac n2)个数的差!

计算最优答案

考虑最优的答案,肯定就是从每种颜色中各取(k)张,用其中较大的(frac {nk}2)个数减去较小的(frac{nk}2)个数能得到的最大值。

容易发现,对于一种选法,一定能构造出一种方案来实现。

那么现在先考虑计算最优答案。

显然,对于一种颜色,其中造成正贡献的肯定是最大的一些数,造成负贡献的肯定是最小的一些数。

我们不妨先令所有颜色都选出最小的(k)个数作为负贡献。

接下来只要进行(frac {nk}2)次调整,对于一种颜色贡献就是把最大的负贡献替换成未选中的最大数作为正贡献。

由于最大的负贡献不断减小,未选中的最大数也不断减小,故一种颜色能造成的贡献是不断减小的,于是我们就可以愉快而套路地用堆优化。

构造合法方案

这种东西直接贪心构造应该就可以了。

我们按造成正贡献的数从多到少枚举每一种颜色,然后贪心地把它们分配到已有正贡献的数最少的这些天里。

这一过程同样可以用一个堆来维护。

代码:(O(nlogn))

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 1500
#define LL long long
#define Array vector<vector<int> >
using namespace std;
int n,m,w[N+5],id[N+5][N+5],op[N+5][N+5],pos[N+5],ct[N+5],St[N+5][N+5],used[N+5][N+5];
struct S
{
	int x,v;I S(CI a=0,CI b=0):x(a),v(b){}
	I bool operator < (Con S& o) Con {return v<o.v;}
}P[N+5];priority_queue<S> Q;
I bool cmp(CI x,CI y) {return w[x]<w[y];}
I bool cmp2(CI x,CI y) {return ct[x]>ct[y];}
Array s;LL find_maximum(int k,Array x)
{
	RI i,j;LL t=0;for(n=x.size(),m=x[0].size(),s.resize(n),i=0;i^n;++i)//初始化
		{for(j=0;j^m;++j) s[i].push_back(-1),w[id[i][j]=j]=x[i][j];sort(id[i],id[i]+m,cmp);}//对于每种颜色中值的大小排序
	#define G(p) S(p,x[p][m-ct[p]-1]+x[p][k-ct[p]-1])
	for(i=0;i^n;Q.push(G(i)),pos[i]=i,++i) for(j=0;j^k;++j) op[i][id[i][j]]=-1,t-=x[i][id[i][j]];//初始化所有颜色全选负贡献
	RI p,q;for(i=1;i<=n*k/2;++i) p=Q.top().x,t+=Q.top().v,Q.pop(),//堆套路
		++ct[p],op[p][id[p][k-ct[p]]]=0,op[p][id[p][m-ct[p]]]=1,ct[p]<k&&(Q.push(G(p)),0);//ct记录已选择正贡献数的个数
	W(!Q.empty()) Q.pop();for(i=0;i^k;++i) Q.push(S(i,n/2));//重新利用堆
	RI T=0;for(sort(pos,pos+n,cmp2),i=0;i^n;++i)//按正贡献数从多到少枚举每种颜色
	{
		W(ct[i]) St[i][ct[i]--]=p=Q.top().x,(q=Q.top().v-1)&&(P[++T]=S(p,q),0),Q.pop();//分配到已有正贡献数最少的那些天
		W(T) Q.push(P[T--]);
	}
	for(i=0;i^n;++i) for(p=j=0;j^m;++j) op[i][j]==1&&(used[i][s[i][j]=St[i][++p]]=1);//具体赋值
	for(i=0;i^n;++i) for(p=j=0;j^m;++j) if(op[i][j]==-1) {W(used[i][p]) ++p;s[i][j]=p++;}//不是正贡献的天都是负贡献
	return allocate_tickets(s),t;
}
败得义无反顾,弱得一无是处
原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu6831.html