• [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;
    }
    
    既然选择了远方,便只顾风雨兼程
  • 相关阅读:
    ConcurrentHashMap使用示例
    vss的ss.ini丢失或损坏导致的vss无法登录错误
    Arcgis中用滚轮做放大缩小为什么和一般软件反向
    MapControl控件
    string截取字符串
    C# CheckedListBox控件用法总结(怎样得到多选的值)
    通信串口中报ObjectDisposedException错误时怎么解决
    C#串口SerialPort常用属性方法
    SerialPort.DataReceived 事件
    C#的串口编程
  • 原文地址:https://www.cnblogs.com/lindalee/p/11364975.html
Copyright © 2020-2023  润新知