题意
一共有n 位评委,他们每人可以打1 分或0 分,第i 位评委希望选手的得分为v[i]。评委们会按一个顺序依次评分,第一个评分的评委打0 分。对于接下来的评委,假设前面a 位评委评分总和为b,评委会认为这位选手期望得分为b/a*n,如果这个得分低于他所希望的得分,他会打1 分,否则他会打0 分。你希望选手的得分为p(0<=p<=n),为此你可以调换评委们的评分顺序。你需要输出一个1~n 的排列,第i 个位置表示第i 个评分的裁判的编号,让选手的得分最接近p。如果有多种,你只需要输出任意一种。
n<=100000
分析
首先有一个结论:把裁判按期望分数从小到大排序可以得到最高分,从大到小排序可以得到最低分。
我们找到最高分和最低分,设为l和r,不难证明区间[l,r]内的任意一个分数我都可以取到。这时分数要么取最高分要么取最低分要么取p。
只考虑如何恰好取到p。
我们可以考虑先弄一些数让分数尽量大,然后剩下的数再让分数尽量小。
那么可以取一个分界点,先把大于分界点的数从小到大排列,然后再把不大于分界点的数从大到小排列。
不难发现随着分界点的递增,分数是单调不升的,这样我们就只要二分一下即可。
至于为什么这样做可以保证取完[l,r]中的所有数,我也不是很会证明。
代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N=100005;
int n,p,v[N],f[N],d[N];
bool cmp(int x,int y)
{
return v[x]<v[y];
}
int calc()
{
int sco=0;
for (int i=2;i<=n;i++)
if ((LL)sco*n<(LL)v[d[i]]*(i-1)) sco++;
return sco;
}
int check(int mid)
{
int tot=0;
for (int i=mid+1;i<=n;i++) d[++tot]=f[i];
for (int i=mid;i>=1;i--) d[++tot]=f[i];
return calc();
}
int main()
{
scanf("%d%d",&n,&p);
for (int i=1;i<=n;i++) scanf("%d",&v[i]),f[i]=i;
sort(f+1,f+n+1,cmp);
int mn,mx;
for (int i=1;i<=n;i++) d[i]=f[i];
mx=calc();
for (int i=1;i<=n;i++) d[i]=f[n-i+1];
mn=calc();
if (p<=mn)
{
for (int i=n;i>=1;i--) printf("%d ",v[f[i]]);
return 0;
}
if (p>=mx)
{
for (int i=1;i<=n;i++) printf("%d ",v[f[i]]);
return 0;
}
int l=1,r=n;
while (l<=r)
{
int mid=(l+r)/2,w=check(mid);
if (w==p) break;
else if (w<p) r=mid-1;
else l=mid+1;
}
for (int i=1;i<=n;i++) printf("%d ",v[d[i]]);
return 0;
}