CS Academy Round 54 Voting 二分

题意

一共有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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值