【贪心 / 线段树模拟费用流增广】BZOJ4977 [Lydsy八月月赛] 跳伞求生

【题目】

原题地址
n 个队友和 m 个敌人,每个队友有一个攻击力 ai ,每个敌人有攻击力 bi 和价值 ci 。你可以选择若干个队友,每个队友 i 分别去怼一个敌人 j ,当 ai>bj 时,你的队友可以对答案造成 aibj+cj 的贡献。求最大贡献。

【题目分析】
看到题目第一眼就是贪心了。但是贪心的策略以及正确性还是要商讨的。

【解题思路】
先将 a b 放在一起从小到大排序,相等的话 a 放前面。然后开一个优先队列维护敌人价值 cb
对于每一个 ai ,如果当前在优先队列中有敌人可以打,那么就打掉。
否则我们可以丢掉一个 a 最小的队友来代替,显然是更优的。
做完这个操作以后我们会得到一个选择最多队友情况下的最优解,但不一定是本题要求的最优( cb 可能是负数)
那我们怎么解决这个问题呢?
显然就可以每次丢掉一个 a 最小的队友,同时丢掉一个 cb 最小的敌人,我们可以得到一组新的解,而且这组解可能更优(当然也会变差就是了,不过还是贪心的思想233)。
而且这样显然也是满足题目要求的。(因为本来就是一一对应的关系,先删小的队友不会不合法)
这样这道题目就可以通过了。

你以为这就结束了吗?(实际上确实结束了)

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

我看完以后WA地一声就哭了出来。
于是很不甘心地把线duang树版本也写了一次。

【参考代码】

贪心

#include
using namespace std;

typedef long long LL;
const int N=2e5+10;
int n,m,head,tail;
int w[N];
LL ans,sum;
priority_queue<int>q;
priority_queue<int,vector<int>,greater<int> >p;

struct Tdata
{
    int x,y,typ;
};
Tdata a[N];

bool cmp(Tdata A,Tdata B)
{
    if(A.x==B.x)
        return A.typreturn A.xint main()
{
    freopen("BZOJ4977.in","r",stdin);
    freopen("BZOJ4977.out","w",stdout);

    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)
        scanf("%d",&a[i].x),a[i].typ=0;
    for(int i=1;i<=m;++i)
    {
        ++n;
        scanf("%d%d",&a[n].x,&a[n].y);
        a[n].typ=1;a[n].y-=a[n].x;
    }
    sort(a+1,a+n+1,cmp);

    head=1;
    for(int i=1;i<=n;++i)
    {
        if(a[i].typ)
            q.push(a[i].y);
        else
        {
            if(q.empty())
            {
                if(head<=tail)
                {
                    sum+=a[i].x-w[head++];
                    w[++tail]=a[i].x;
                }
            }
            else
            {
                int tmp=q.top();q.pop();
                sum+=a[i].x+tmp;
                p.push(tmp);
                w[++tail]=a[i].x;
            }
        }
    }

    ans=sum;
    for(int i=head;i<=tail;++i)
    {
        sum-=p.top()+w[i];
        p.pop();
        ans=max(ans,sum);
    }
    printf("%lld\n",ans);

    return 0;
}

线段树

#include
using namespace std;

typedef long long LL;
const int INF=1e9;
const int N=1e5+10;

struct Tnode
{
    int w,f;
};
Tnode g[N<<1],d[N];
LL ans;
int i,j,k,n,m,a[N],x,y,h,w[N<<1],c[N<<3],p[N<<3];

inline bool Cmp(Tnode a,Tnode b)
{
    if(a.w==b.w)
        return a.freturn a.winline void pushdown(int x)
{
    if(p[x])
    {
        c[x<<1]+=p[x];p[x<<1]+=p[x];
        c[x<<1|1]+=p[x];p[x<<1|1]+=p[x];
        p[x]=0;
    }
}

inline void update(int x,int l,int r,int L,int R,int y)
{
    if(l>R || rreturn;
    if(l>=L && r<=R)
    {
        c[x]+=y;p[x]+=y;
        return;
    }
    pushdown(x);
    int mid=(l+r)>>1;
    if(L<=mid)
        update(x<<1,l,mid,L,R,y);
    if(R>mid)
        update(x<<1|1,mid+1,r,L,R,y);
    c[x]=min(c[x<<1],c[x<<1|1]);
}

inline int query(int x,int l,int r,int L,int R)
{
    if(l>R || rreturn INF;
    if(l>=L&&r<=R)
        return c[x];
    pushdown(x);
    int mid=(l+r)>>1,ret=INF;
    if(L<=mid)
        ret=min(ret,query(x<<1,l,mid,L,R));
    if(R>mid)
        ret=min(ret,query(x<<1|1,mid+1,r,L,R));
    return ret;
}

int main()
{
    freopen("BZOJ4977.in","r",stdin);
    freopen("BZOJ4977.out","w",stdout);

    scanf("%d%d",&n,&m);
    h=n+m;
    for(i=1;i<=n;++i)
        scanf("%d",&a[i]);
    sort(a+1,a+n+1);

    for(i=1;i<=n;++i)
        g[i].w=a[i],g[i].f=i;
    for(i=1;i<=m;++i)
    {
        scanf("%d%d",&x,&y);
        g[i+n].w=x;g[i+n].f=i+n;
        d[i].w=y-x;d[i].f=i;
    }
    sort(g+1,g+h+1,Cmp);sort(d+1,d+m+1,Cmp);

    for(i=1;i<=h;++i)
        w[g[i].f]=i;
    for(i=n,j=m;i;--i,--j)
    {
        update(1,1,h,w[i],h,-1);
        for(;j;--j)
        {
            k=w[d[j].f+n];
            if(query(1,1,h,1,k-1)>=0 && query(1,1,h,k,h)>=-1&&a[i]+d[j].w>0)
            {
                ans+=a[i]+d[j].w;
                update(1,1,h,k,h,1);
                break;
            }
        }
        if(!j)
            break;
    }
    printf("%lld\n",ans);

    return 0;
}

【总结】
仔细思考了以后,发觉贪心的思想和这个线duang树的思想本质上是一样的。
那么我觉得还是写贪心吧qwq。
不过这个线段树模拟费用流增广的思想还是很实用的,毕竟罗指导之前是给我们讲过另一道这种思想的题目呢qwq。

你可能感兴趣的:(数据结构-线段树,其他-贪心)