[CF1539E]Game with Cards
Posted Arextre
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[CF1539E]Game with Cards相关的知识,希望对你有一定的参考价值。
壹、题目描述 ¶
贰、题解 ¶
做过一道十分类似的题:传送门。然而当时似乎没有特别好地理解转移的妙处。
事实上,这道题的 \\(\\rm DP\\) 关键在于 —— 对于交换点进行 \\(\\rm DP\\).
何为交换点?比如
即某只手开始不间断地接受新卡牌的点。
因为我们每次一定有一只手接受新的卡牌,而不操作的手停留在最后一次接受卡牌的地方,所以,我们需要记录的就是 —— 当前考虑到哪个位置,当前接受新卡牌的手是左手还是右手,以及另一只手现在持有的卡牌是前面的哪一张 这三个因素,比较巧妙的是,当前考虑到哪个位置,当前接受新卡牌的手是左手还是右手 这两个因素组合起来,可以涵盖住 接受卡牌的手拿的是哪张卡牌 这一状态
不难设计出 \\(\\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的主要内容,如果未能解决你的问题,请参考以下文章
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)