[CF1539E]Game with Cards

Posted Arextre

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[CF1539E]Game with Cards相关的知识,希望对你有一定的参考价值。

壹、题目描述 ¶

传送门 to Luogu.

贰、题解 ¶

做过一道十分类似的题:传送门。然而当时似乎没有特别好地理解转移的妙处。

事实上,这道题的 \\(\\rm DP\\) 关键在于 —— 对于交换点进行 \\(\\rm DP\\).

何为交换点?比如

\\[\\begin{matrix} \\color{red}0&0&0&0&0&\\color{red}1&1&1&1&\\color{red}0&0&\\cdots \\end{matrix} \\]

即某只手开始不间断地接受新卡牌的点。

因为我们每次一定有一只手接受新的卡牌,而不操作的手停留在最后一次接受卡牌的地方,所以,我们需要记录的就是 —— 当前考虑到哪个位置,当前接受新卡牌的手是左手还是右手,以及另一只手现在持有的卡牌是前面的哪一张 这三个因素,比较巧妙的是,当前考虑到哪个位置,当前接受新卡牌的手是左手还是右手 这两个因素组合起来,可以涵盖住 接受卡牌的手拿的是哪张卡牌 这一状态

不难设计出 \\(\\rm DP\\),定义 \\(f_{0|1,i,j}\\) 表示当前接受新卡牌的手是左手还是右手,当前考虑到哪个位置,另一只手现在持有的卡牌是前面的哪一张,这样转移是 \\(\\mathcal O(n^2)\\) 的。

但是由于我们只需要判断可行性,我们不需要整个 \\(f\\) 数组,而是可以维护两个集合 \\(f_0,f_1\\),下标即当前哪只手在接受新的卡牌,集合中储存的是另一只手持有的数以及对应下标,在当前仍然是合法的。

假设当前枚举到询问 \\(i\\).

首先,若 \\(|f_1|>0\\),那么我们可以从 \\(f_1\\) 转移到左手来,即当前我们持有手是左手 \\(0\\),右手持有的卡牌是 \\(k_{i-1}\\),其下标对应为 \\(i-1\\)(方便输出路径),将 \\(\\lang k_{i-1},i-1\\rang\\) 加入集合。

其次,如果 \\(k_i\\in {\\Large[}\\text{LeftHandLeft}_i,\\text{LeftHandRight}_i{\\Large]}\\),即当前的卡仍然可以被左手接受,那么,就接受,但是在接受之后,需要保证 \\(f_0\\) 中的所有元素属于 \\({\\Large[}\\text{RightHandLeft}_i,\\text{RightHandRight}_i{\\Large]}\\),将不在这个区间中的数全部删掉。

但是如果 \\(k_i\\notin {\\Large[}\\text{LeftHandLeft}_i,\\text{LeftHandRight}_i{\\Large]}\\),那么所有 \\(f_0\\) 的状态都不合法了,因为左手是不能接受 \\(k_i\\) 这张卡牌的。

右手同理。记录路径时,任意找一个合法前驱记录即可。

时间复杂度 \\(\\mathcal O(n\\log n)\\),但是决策点有单调性,即越近的点越好转移(因为需要满足的条件是许多集合的并),如果倒着来,甚至可以做到 \\(\\mathcal O(n)\\).

叁、参考代码 ¶

#include<cstdio>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<set>
using namespace std;

// #define NDEBUG
#include<cassert>

namespace Elaina{
    #define rep(i, l, r) for(int i=(l), i##_end_=(r); i<=i##_end_; ++i)
    #define drep(i, l, r) for(int i=(l), i##_end_=(r); i>=i##_end_; --i)
    #define fi first
    #define se second
    #define mp(a, b) make_pair(a, b)
    #define Endl putchar(\'\\n\')
    #define mmset(a, b) memset(a, b, sizeof a)
    // #define int long long
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int, int> pii;
    typedef pair<ll, ll> pll;
    template<class T>inline T fab(T x){ return x<0? -x: x; }
    template<class T>inline void getmin(T& x, const T rhs){ x=min(x, rhs); }
    template<class T>inline void getmax(T& x, const T rhs){ x=max(x, rhs); }
    template<class T>inline T readin(T x){
        x=0; int f=0; char c;
        while((c=getchar())<\'0\' || \'9\'<c) if(c==\'-\') f=1;
        for(x=(c^48); \'0\'<=(c=getchar()) && c<=\'9\'; x=(x<<1)+(x<<3)+(c^48));
        return f? -x: x;
    }
    template<class T>inline void writc(T x, char s=\'\\n\'){
        static int fwri_sta[1005], fwri_ed=0;
        if(x<0) putchar(\'-\'), x=-x;
        do fwri_sta[++fwri_ed]=x%10, x/=10; while(x);
        while(putchar(fwri_sta[fwri_ed--]^48), fwri_ed);
        putchar(s);
    }
}
using namespace Elaina;

const int maxn=1e5;
const int inf=0x3f3f3f3f;

int k[maxn+5], n;
pii left[maxn+5], right[maxn+5];

/** 
 * @brief the index means now which hand is accepting the new card, 
 * @param first the holding number of the other hand
 * @param second the staring holding point
*/
set<pii>f[2];
int to[maxn+5][2];

inline int inside(int x, pii interval){
    return interval.fi<=x && x<=interval.se;
}

void print(int i, int cur){
    if(!i) return;
    print(to[i][cur], cur^1);
    rep(j, to[i][cur]+1, i) writc(cur, \' \');
}

signed main(){
    n=readin(1), readin(1);
    rep(i, 1, n){
        k[i]=readin(1);
        left[i].fi=readin(1), left[i].se=readin(1);
        right[i].fi=readin(1), right[i].se=readin(1);
    }
    /** special treatment for the first request */
    if(right[1].fi==0 && inside(k[1], left[1]))
        f[0].insert(mp(0, 0));
    if(left[1].fi==0 && inside(k[1], right[1]))
        f[1].insert(mp(0, 0));
    /** from 2 to n */
    rep(i, 2, n){
        /** record the status, because later we\'ll change it */
        int emp0=f[0].empty(), emp1=f[1].empty();
        if(!emp0) f[1].insert(mp(k[i-1], i-1));
        if(!emp1) f[0].insert(mp(k[i-1], i-1));
        /** try letting left hand hold this card */
        if(inside(k[i], left[i])){
            while(!f[0].empty() && !inside(f[0].begin()->fi, right[i]))
                f[0].erase(f[0].begin());
            while(!f[0].empty() && !inside(f[0].rbegin()->fi, right[i]))
                f[0].erase(f[0].lower_bound(*f[0].rbegin()));
        }
        else f[0].clear(); /** fail to accept */
        if(inside(k[i], right[i])){
            while(!f[1].empty() && !inside(f[1].begin()->fi, left[i]))
                f[1].erase(f[1].begin());
            while(!f[1].empty() && !inside(f[1].rbegin()->fi, left[i]))
                f[1].erase(f[1].lower_bound(*f[1].rbegin()));
        }
        else f[1].clear();
        /** arbitrarily choose a point to be the previous trans point */
        if(!f[0].empty()) to[i][0]=f[0].begin()->se;
        if(!f[1].empty()) to[i][1]=f[1].begin()->se;
    }
    if(!f[0].empty()) printf("yEs\\n"), print(n, 0);
    else if(!f[1].empty()) printf("yEs\\n"), print(n, 1);
    else printf("nO\\n");
    return 0;
}

肆、关键的地方 ¶

不妨称这种 \\(\\rm DP\\) 为分界线 \\(\\rm DP\\)?其适用范围:

  • 有两个接收器;
  • 不能不接受,即每个回合有且仅有一个接收器接受;

以上是关于[CF1539E]Game with Cards的主要内容,如果未能解决你的问题,请参考以下文章

CF777B Game of Credit Cards

Codeforces Round #727 (Div. 2) E. Game with Cards(巧妙dp的优化)

Codeforces1539 E. Game with Cards(思维+dp,st表倍增+二分 预处理)

Codeforces Round #727 (Div. 2) E. Game with Cards(dp优化,从n^2到nlog到n)

CF 1103B Game with modulo

[CF1103B]Game with modulo