Codeforces GYM103102 L. Neo-Robin Hood题解

题意:你是劫富济自己的罗宾汉,有n个富人,第i个人有m[i]元财富,收买他需要p[i]元。对每个人你都可以选择1.抢他,你获得m[i]元,2.不对他进行操作,3.花p[i]元收买他,他为你开脱你的一件抢劫罪行。你有一个奇怪的目标:抢的人数越多越好,但是你的每一个罪行都必须被开脱。  

翻译一下就是抢k个人,收买另外k个人,抢来的钱要足够收买的花费,最大化的k。

 

首先声明,这套题是有官方题解的,但是官方题解有一点谜语人行为,可能在这里讲解官方题解作用不大,具体想交流的人请在评论下留言,我看到后会与你交流!

 

解法:

Part 1:

例如目前假设已经选定了2k个人,剩下的n-2k个人已经无关,设被偷的k个人为集合A,被收买的k个为集合B

假设现在偷A的人偷到了x元,收买B的人要花y元,设z=x-y,我们试图增大z,直到z不小于0(实际上等价于最大化z再检查z是否不小于0)即表明答案不小于k

由于2k个人已经被固定,我们能做的就靠只有交换AB阵营的人,以期偷的钱够收买
关于交换的有公式:
设A里有个人叫r他有a元,收买他需要b元;B里有个人叫s,他有c元,收买他需要d元, 
然后我现在不“偷r收买s”了,我改成“偷s收买r”,那么z就会更新为z+(c+d)-(a+b)
定义tot[i]=m[i]+p[i],这表明如果tot[s]>tot[r],那么偷s收买r比偷r收买s更优


Part 2:

我们对n个人依据tot降序排序

定义一个“分水岭”(可以是1~n任何数),我们将在分水岭左边选k个人进行偷取,在分水岭右边选k个人进行收买。等价的定义是,B中tot的最大值一定小于A中tot的最小值。否则我们可以通过交换A,B中的人来使z更大,得到更优策略。

 

Part 3:

然后二分答案(可以证明,如果偷k人收买k人可以全身而退,那么偷j人收买j人也可以全身而退(j<k)),check 偷k收买k是否可行

利用“分水岭特性”,假设分水岭为i,那么在[1,i]中选k个人进行偷取(当然选k个m最大的啦),在[i+1,n]中选k个人进行收买(当然选k个p最小的啦),就可以判断在这个分水岭下偷k收买k是否可行

具体做法为,对每个i统计[1~i]中k个最大的m,用multiset或优先队列记录,并将总和存入数组frontmax[i]中,统计[i~n]中k个最小的p的总和,用multiset或优先队列记录,并将总和存入数组backmin[i]中,然后比较frontmax[i]与backmin[i+1]即可得到以i为分水岭偷k收买k是否可行。

对每个i都判断一次frontmax[i]>=backmin[i+1],即可得到对于本题偷k收买k是否可行。

 

这次我的代码写得比较清楚,希望被观摩!

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string>
#include<string.h>
#include<cmath>
#include<functional>
#include<numeric>
#include<set>

#define rep(i,a,b) for(ll i=a;i<=b;++i)
#define per(i,a,b) for(ll i=a;i>=b;--i)
#define fi first
#define se second
#define mp make_pair
#define all(x) x.begin(),x.end()
#define debug(x) cout<<# x <<" is "<<x<<endl;

using namespace std;
typedef long long ll;
typedef pair<ll,ll> PII;
const ll maxn=1e5+10,inf=0x3f3f3f3f3f3f3f3f,mod=1e9+7;

struct Man{
    ll m,p,tot;
    bool operator<(const Man& a){
        //非真正的小于:sort后tot大的(次级比较m大的)排在前面,优先偷取
        return tot==a.tot?m>a.m:tot>a.tot;
    }
}man[maxn];

int n;    
ll frontmax[maxn],backmin[maxn];

bool Check(int len){
    memset(frontmax,0,sizeof(frontmax));
    memset(backmin,0x3f,sizeof(backmin));

    int s=len,e=n-len+1;

    multiset<ll> maxset;
    rep(i,1,s) maxset.insert(man[i].m);
    frontmax[s] = accumulate(all(maxset),0ll);    //鄙人因为这里0没有加ll,WA37了555。0也要记得声明成long long 啊!
    rep(i,s+1,e-1) {
        ll x = man[i].m, x0 = *maxset.begin();
        if(x > x0){
            maxset.erase(maxset.begin());
            maxset.insert(x);
            frontmax[i]=frontmax[i-1]-x0+x;
        }
        else frontmax[i]=frontmax[i-1];
    }


    multiset<ll,greater<ll>> minset;
    per(i,n,e) minset.insert(man[i].p);
    backmin[e] = accumulate(all(minset),0ll);
    per(i,e-1,s+1) {
        ll y = man[i].p, y0 = *minset.begin();
        if(y < y0){
            minset.erase(minset.begin());
            minset.insert(y);
            backmin[i]=backmin[i+1]-y0+y;
        }
        else backmin[i]=backmin[i+1];
    }

    rep(i,s,e-1){
        if(frontmax[i]>=backmin[i+1]) return true;
    }
    return false;
}


int main()
{
    scanf("%d",&n);
    rep(i,1,n){
        ll x;    scanf("%lld",&x);
        man[i].m=x;
    }
    rep(i,1,n){
        ll y;    scanf("%lld",&y);
        man[i].p=y;    man[i].tot=man[i].m+y;
    }
    
    sort(man+1,man+1+n);

    int ans=0;
    int L=1,R=n/2;
    while(L<=R){
        int mid=(L+R)>>1;
        if(Check(mid)) {
            ans=mid;
            L=mid+1;
        }
        else R=mid-1;
    }
    printf("%d\n",ans);
    return 0;
}

/*
4
1 4 15 10
20 15 3 5
ans:1

4
12 12 9 8
12 10 13 13
ans:1

6
10 9 17 3 2 1
13 14 3 17 17 18
ans:2
*/
View Code

 

posted @ 2021-06-15 23:01  Laozhu1234  阅读(571)  评论(0编辑  收藏  举报