[bzoj4977] [Lydsy1708月赛] 跳伞求生

Description

(Q) 最近沉迷于《跳伞求生》游戏。他组建了一支由 (n) 名玩家(包括他自己)组成的战队,编号依次为 (1)(n) 。这个游戏中,每局游戏开始时,所有玩家都会从飞机上跳伞,选择一个目的地降落,跳伞和降落的时间有早有晚。在某局游戏降落前,他们在空中观察发现地面上一共有 (m) 间房子,编号依次为 (1)(m) 。其中每间房子恰好有一名敌人早于他们到达。小 (Q) 战队的第i名玩家拥有 (a_i) 发子弹,地面上第i间房子里的敌人拥有 (b_i) 发子弹,消灭他可以获得 (c_i) 点积分。每名玩家必须且只能选择一间房子降落,然后去消灭里面的敌人。若第i名玩家选择了第j间房子,如果 (a_i>b_j) ,那么他就可以消灭该敌人,获得 (a_i-b_j+c_j) 的团队奖励积分,否则他会被敌人消灭。为了防止团灭,小 (Q) 不允许多名玩家选择同一间房子,因此如果某位玩家毫无利用价值,你可以选择让他退出游戏。因为房子之间的距离过长,你可以认为每名玩家在降落之后不能再去消灭其它房间里的敌人。作为小 (Q) 战队的指挥,请制定一套最优的降落方案,使得最后获得的团队奖励总积分最大。

Input

第一行包含两个正整数 (n) , (m) ( $1 leq n,m leq 100000 $ ),分别表示战队的玩家数和地面上的房间数。
第二行包含 (n) 个正整数 (a_1,a_2,...,a_n) ( (1 leq a_i leq 100000) ),分别表示每个玩家的子弹数。
接下来 (m) 行,每行两个正整数 (b_i,c_i) ( (1 leq b_i,c_i leq 100000) ),分别表示每个敌人的子弹数和奖励积分。

Output

输出一行一个整数,即最后获得的团队奖励总积分的最大值。

Sample Input

3 3

4 4 4

2 3

1 3

5 3

Sample Output

11


想法

神仙题!貌似有两种做法——

做法一:贪心

(a_i)(b_i) 将玩家和敌人分别 从小到大排序
从小到大贪心考虑每名玩家,用两个优先队列维护,瞎搞一搞。。。
交上去发现 (WA) 了……原因是这样做是让发挥作用的玩家数最多,但不一定是总分最大
(有可能 (c_i-b_i) 的值为负且绝对值大于某一个 (a_i) ,那么把他们俩都去掉后总分更大)
那么把贪心改进一下,在上面的贪心进行完后,每次去掉 (a_i) 最小的玩家和 (c_i-b_i) 最小的敌人(可以保证这样做后配对仍合法),更新答案。
据说这样是可以 (A) 掉的。(然而我莫名 (WA) 了【逃】)

做法二:线段树模拟费用流

放一下官方题解:

不难发现 (a) 越大的玩家越容易占领房子,并且收益越大,因此最优解中一定是选 (a) 最大的若干个玩家
为了方便起见,我们设 (v[i]=c[i]−b[i]) ,即每个房子的收益。
将玩家和房子混在一起,按 (a)(b) 从小到大排序,对于相同的情况,将玩家优先放在前面,那么每个玩家能占领的房子就是它前面的所有房子
如果我们将方案中选取的玩家看成右括号,房子看成左括号,那么方案必定是一个合法的括号序列
即设 (s[i]) 表示前 (i) 个位置中选取的房子减去玩家的个数,那么必有 (min(s[i]) leq 0)(s[n+m]=0)
因为 (a) 越大越好,因此我们从大到小考虑每个玩家
对于当前这个玩家,我们先将其位置填上右括号,对应 (s) 中一段后缀减去 (1)
我们希望找到一个没用过的收益最大的房子,满足加入那个房子后仍然是一个合法的括号序列
假设房子位于位置 (j) ,那么加入 (j) 会导致 (s[j..n+m]) 加上(1)
因此只要 (min(s[1..j−1]) leq 0)(min(s[j..n+m]) geq −1) 即是合法的房子
而区间加减、 区间最小值查询则是线段树的经典操作
(v) 从大到小考虑每个未使用的房子,如果它合法,那么将其纳入答案,同时加入该左括号,然后考虑下一个玩家
如果非法,因为我们按照 (a) 从大到小考虑每个玩家,今后的条件只会越来越苛刻,因此它永远都不可能合法,直接抛弃即可
注意到上述问题本质是在用线段树模拟费用流的增广,因此当增广路长度 (<0) 时,即可终止算法
时间复杂度 (O((n+m)log(n+m)))

既然是模拟费用流,那我就画一下费用流的图长啥样子——

我们需要的是最大费用流(但不一定是最大流),所以找最长路,当最长路长度 (<0) 时终止
题解中的左右括号是非常形象的理解方式【%%%】
(s[i]) 就相当于中间那一坨边的 逆流边 可流的流量多少,这样就解决了逆流的问题~太巧妙了!


代码

(WA) 了好久好久……

#include<cstdio>
#include<iostream>
#include<algorithm>
 
using namespace std;
 
int read(){
    int x=0;
    char ch=getchar();
    while(!isdigit(ch)) ch=getchar();
    while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
    return x;
}
 
const int N = 100005;
typedef long long ll;
 
int n,m,w;
struct data{
    int a,c;
    bool operator < (const data &b) const { return a<b.a || (a==b.a && c<b.c); }
}d[N*2];
struct pos{
    int x,id;
    pos() { x=id=0; }
    pos(int _x,int _id) { x=_x; id=_id; }
    bool operator < (const pos &b) const { return x>b.x; }
}po[N];
 
struct tree{
    tree *ch[2];
    int Min,lazy;
}pool[N*4],*root;
int cnt;
void build(tree *p,int l,int r){
    p->Min=p->lazy=0;
    if(l==r) return;
    int mid=(l+r)>>1;
    build(p->ch[0]=&pool[++cnt],l,mid);
    build(p->ch[1]=&pool[++cnt],mid+1,r);
}
void pushdown(tree *p){
    if(!p->lazy) return;
    p->ch[0]->Min+=p->lazy; p->ch[0]->lazy+=p->lazy;
    p->ch[1]->Min+=p->lazy; p->ch[1]->lazy+=p->lazy;
    p->lazy=0;
}
void add(tree *p,int l,int r,int L,int R,int c){
    if(l==L && r==R) { p->lazy+=c; p->Min+=c; return; }
    pushdown(p);
    int mid=(l+r)>>1;
    if(R<=mid) add(p->ch[0],l,mid,L,R,c);
    else if(L>mid) add(p->ch[1],mid+1,r,L,R,c);
    else{
        add(p->ch[0],l,mid,L,mid,c);
        add(p->ch[1],mid+1,r,mid+1,R,c);
    }
    p->Min=min(p->ch[0]->Min,p->ch[1]->Min);
}
int Min(tree *p,int l,int r,int L,int R){
    if(L>R) return 0;
    if(l==L && r==R) return p->Min;
    pushdown(p);
    int mid=(l+r)>>1;
    if(R<=mid) return Min(p->ch[0],l,mid,L,R);
    else if(L>mid) return Min(p->ch[1],mid+1,r,L,R);
    return min(Min(p->ch[0],l,mid,L,mid),Min(p->ch[1],mid+1,r,mid+1,R));
}
 
int main()
{
    n=read(); m=read();
    w=n+m;
    for(int i=1;i<=n;i++) d[i].a=read(),d[i].c=0;
    for(int i=1;i<=m;i++) d[i+n].a=read(),d[i+n].c=read();
     
    sort(d+1,d+1+w);
    int t=0;
    for(int i=1;i<=w;i++)
        if(d[i].c) po[++t]=pos(d[i].c-d[i].a,i);
    sort(po+1,po+1+m);
     
    build(root=&pool[++cnt],1,w);
    ll ans=0; t=1;
    for(int i=w;i>0;i--){
        if(d[i].c) continue;
        add(root,1,w,i,w,-1);
        while(t<=m){
            if(Min(root,1,w,1,po[t].id-1)>=0 && po[t].x+d[i].a>0) break;
            t++;
        }
        if(t<=m) {
            ans+=po[t].x+d[i].a;
            add(root,1,w,po[t].id,w,1);
            t++;
        }
        else break;
    }
    printf("%lld
",ans);
     
    return 0;
}
既然选择了远方,便只顾风雨兼程
原文地址:https://www.cnblogs.com/lindalee/p/11364975.html