题意
小Q最近沉迷于《跳伞求生》游戏。他组建了一支由n名玩家(包括他自己)组成的战队,编号依次为1到n。这个游戏中,每局游戏开始时,所有玩家都会从飞机上跳伞,选择一个目的地降落,跳伞和降落的时间有早有晚。在某局游戏降落前,他们在空中观察发现地面上一共有m间房子,编号依次为1到m。其中每间房子恰好有一名敌人早于他们到达。小Q战队的第i名玩家拥有 ai 发子弹,地面上第i间房子里的敌人拥有 bi 发子弹,消灭他可以获得 ci 点积分。每名玩家必须且只能选择一间房子降落,然后去消灭里面的敌人。若第i名玩家选择了第j间房子,如果 ai>bj ,那么他就可以消灭该敌人,获得 ai−bj+cj 的团队奖励积分,否则他会被敌人消灭。为了防止团灭,小Q不允许多名玩家选择同一间房子,因此如果某位玩家毫无利用价值,你可以选择让他退出游戏。因为房子之间的距离过长,你可以认为每名玩家在降落之后不能再去消灭其它房间里的敌人。作为小Q战队的指挥,请制定一套最优的降落方案,使得最后获得的团队奖励总积分最大1<=n,m,ai,bi,ci<=100000
分析
做法十分巧妙的一题。
首先有个很显然的结论就是我们用的玩家肯定是战斗力越大越好,所以选的玩家一定是a最大的若干个。我们先把敌人和玩家放在一次,按照它们的子弹数排序,若子弹数相同的玩家优先。那么每个玩家可以匹配的敌人就一定是在它的前面。
考虑把敌人看成左括号,玩家看成右括号,那么结果一定是一个合法的括号序。我们定义s[i]表示前i个位置已经选了的敌人数量减去已选了的玩家数量,也就是左括号减去右括号。那么对于一个合法的方案,必然有min(s[i])>=0,s[n+m]=0。考虑把玩家按照a从大到小排序后逐个加入。当加入一个玩家时,我们把对应位置填上右括号,把对应的一段s减去1。这时我们想要选择一个收益最大且满足条件的敌人加入。而一个敌人pos满足条件,当且仅当min(s[1..pos-1])>=1且min(s[pos…n+m])>=-1。那么我们可以按照收益从大到小来枚举敌人。若能加入则加入,否则就直接扔掉。因为我们加入的玩家是按a从大到小枚举的,而如果当前的敌人不能加入,因为以后的限制会越来越紧,所以以后也不能加入。那么我们可以直接用线段树来实现这个过程。注意到这个过程实际上就相当于费用流的增广,所以当增广路<0时我们就退出。
总结一下,对于这种按照权值一一配对的题,我们可以把所有元素放在一起按权值排序,然后考虑维护括号序之类的做法。
代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N=100005;
const int inf=0x3f3f3f3f;
int n,m;
struct data{int val,b,c,op,pos,id;}a[N],b[N],c[N*2];
struct tree{int mn,tag;}t[N*10];
int read()
{
int x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
bool cmp(data a,data b)
{
return a.val<b.val||a.val==b.val&&a.op<b.op;
}
bool cmp1(data a,data b)
{
return a.val>b.val||a.val==b.val&&a.pos>b.pos;
}
bool cmp2(data a,data b)
{
return a.c-a.b>b.c-b.b;
}
void pushdown(int d,int l,int r)
{
if (l==r||!t[d].tag) return;
int w=t[d].tag;t[d].tag=0;
t[d*2].mn+=w;t[d*2+1].mn+=w;
t[d*2].tag+=w;t[d*2+1].tag+=w;
}
void ins(int d,int l,int r,int x,int y,int z)
{
if (x>y) return;
pushdown(d,l,r);
if (l==x&&r==y)
{
t[d].tag+=z;t[d].mn+=z;
return;
}
int mid=(l+r)/2;
ins(d*2,l,mid,x,min(y,mid),z);
ins(d*2+1,mid+1,r,max(x,mid+1),y,z);
t[d].mn=min(t[d*2].mn,t[d*2+1].mn);
}
int query(int d,int l,int r,int x,int y)
{
if (x>y) return inf;
pushdown(d,l,r);
if (l==x&&r==y) return t[d].mn;
int mid=(l+r)/2;
return min(query(d*2,l,mid,x,min(y,mid)),query(d*2+1,mid+1,r,max(x,mid+1),y));
}
int main()
{
n=read();m=read();
for (int i=1;i<=n;i++) a[i].val=read(),c[i].val=a[i].val,c[i].op=0,c[i].id=i;
for (int i=1;i<=m;i++) b[i].b=read(),b[i].c=read(),c[i+n].val=b[i].b,c[i+n].op=1,c[i+n].id=i;
sort(c+1,c+n+m+1,cmp);
for (int i=1;i<=n+m;i++)
if (!c[i].op) a[c[i].id].pos=i;
else b[c[i].id].pos=i;
sort(a+1,a+n+1,cmp1);
sort(b+1,b+m+1,cmp2);
LL ans=0;int p=1;
for (int i=1;i<=n;i++)
{
ins(1,1,n+m,a[i].pos,n+m,-1);
int flag=0;
while (p<=m)
{
if (query(1,1,n+m,b[p].pos,n+m)>=-1&&query(1,1,n+m,1,b[p].pos-1)>=0)
{
if (a[i].val-b[p].b+b[p].c<0) break;
flag=1;ans+=a[i].val-b[p].b+b[p].c;ins(1,1,n+m,b[p].pos,n+m,1);p++;
break;
}
p++;
}
if (!flag) break;
}
printf("%lld",ans);
return 0;
}