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 */