題意:你是劫富濟自己的羅賓漢,有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 */