洛谷 P2123 皇后游戏

前言

在 oi-wiki 的贪心部分下面看到的这题。当然,之前做国王游戏的时候就知道了这道题,但是在当时国王游戏就已经是极限难度了,所以并没有深究。下午看了这道题,用要写博客这件事逼着自己完全理解,结果似乎因为划水所以花了很长时间......

思路

首先,既然是皇后游戏,那么一定和国王游戏有联系(大胆猜测),考虑到国王游戏使用的是 邻项交换法,我们试着在这一题中也推出相应的式子。

不会 $\LaTeX$,故手写(

设当前相邻的两位为 $I,J$

第 $i$ 位大臣 左手上的硬币为 $l_i$ 右手为 $r_i$ 获得的硬币数量为 $v$

$I$ 的左右手分别为$a,b$;$J$ 的左右手分别为$c,d$

设 \sum_{i=1}^na_i,设 h = ci-1

那么有

当 I 在前

 

当 J 在前

最后令 I,J 之中靠后的一方更小可以得到

也就是说,只要满足这个式子,我们得到的解就是最优的。

但是很显然,两边都 hbd 这一项。可以考虑去掉,只考虑剩下两项。因为

所以只要我们控制

就可以满足上述式子,这个式子相当于一个充分条件。化简这个式子

这样,这个式子中就只剩下和第 I,J 项有关的数据了。

然后,就像国王游戏那样,我们对所有大臣进行一个这样的排序,但是会发现,这样其实是不行的。很多题解都给出了一组 hack 数据,这里不做赘述,但是对于错误的原因要详细地说。

在对 I,J 进行比较的时候,我的定义是,如何让这两个大臣接在某个大臣之后,可以使得靠后的大臣价值更小。但是,不妨设想这样的一个场景。

在某个大臣后有三个大臣 A B C,此时他们的位置是 A B C。A 和 B 满足关系,B 和 C 满足关系,但是 A 和 C 不满足位置关系,也就是 C 应该在 A 的前面。使用 C++ 的 sort 之时,因为这种情况下相邻两项相相比都满足条件,程序便不再去改变这个序列,很容易导致错解。这要求我们设置的比较规则应该满足 传递性 这样,如果 A < B 且 B < C ,那么一定有 A < C,不会导致错解。

那么,我们思考一种方式,使得这种方式既有传递性,又可以满足上述式子 min(a,d)<=min(b,c)。

这里给出一种

将所有大臣分成三类,左手更小,左右手相同,右手更小,并且将这三种依此排列好。

对于同种大臣,左手更小的按照左手升序排列,右手更小的按照右手降序排列,左右手相同的随意排列。

容易证明,这样的比较方式具有传递性而且能满足上述式子(我是想不出来的)。

根据这样的方式 sort 一遍,然后按部就班求好答案就好了。

实现

看懂思路就可以直接上手写了。细节...似乎也不是很多,集中在排序上。

#include<bits/stdc++.h>
#define int long long
using namespace std;

inline void read(int&x)
{
    x=0;bool f=1;
    char ch=getchar();
    while(ch<48||ch>57){f=!(ch==45);ch=getchar();}
    while(ch>=48&&ch<=57){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
    x=f?x:-x;
}

int T;
struct node
{
    int l,r;
    bool operator<(const node x)const
    {
        if(l<r&&x.l>=x.r)return 1;
        if(l>r&&x.l<=x.r)return 0;
        
        if(l==r&&x.l<x.r)return 0;
        if(l==r&&x.l>x.r)return 1;
        
        if(l<r&&x.l<x.r)
        {
            return l<x.l;
        }
        if(l>r&&x.l>x.r)
        {
            return r>x.r;
        }
        if(l==r&&x.l==x.r)
        {
            return l<x.l;
        }
    }
}p[20001];

signed main(void)
{
    read(T);
    while(T--)
    {
        int n;read(n);
        for(register int i=1;i<=n;++i)
        {
            read(p[i].l);
            read(p[i].r);
        }
        sort(p+1,p+n+1);
        int ans=p[1].l+p[1].r;
        int tot=p[1].l;
        for(register int i=2;i<=n;++i)
        {
            tot+=p[i].l;
            ans=max(ans,max(tot,ans)+p[i].r);
        }
        printf("%lld\n",ans);
    }
    return 0;
}

结语

  于邻项交换法的理解更深一层了。

Update 2021/8/21 将部分公式更换为 $\LaTeX$

posted @ 2021-07-30 03:13  imfkwk  阅读(143)  评论(0编辑  收藏  举报