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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM