[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\)?其适用范围:

  • 有两个接收器;
  • 不能不接受,即每个回合有且仅有一个接收器接受;
posted @ 2021-06-24 21:52  Arextre  阅读(182)  评论(0编辑  收藏  举报