[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\)?其适用范围:
- 有两个接收器;
- 不能不接受,即每个回合有且仅有一个接收器接受;