【洛谷P5652】基础博弈练习题
博弈
题目大意:
给定一个数列 $a_i$,每个数均为正整数。每次会取区间 $[l,r]$ 进行游戏。规则如下:
开始时棋子在 $l$ 位置,并使得 $a_l$ 减去 $1$。从先手开始,每次向右移动 $0\sim m$ 格(不能超过 $r$),并且满足移动到的格子的 $a_i>0$,然后令 $a_i$ 减去 $1$。不能移动者输。问先手是否必胜。
每次询问独立。
题解:
我们从最后一格开始考虑。
如果 $a_r$ 是奇数,那么先手走到了 $a_r$ 后,两人就只能在这个格子上一直跳。最终先手获胜。那么容易得到,先手走到 $a_{r-m}\sim a_{r-1}$ 都是必败的,就转化为看 $a_{r-m-1}$ 的奇偶性
如果 $a_r$ 是偶数,那么先手走到 $a_r$ 就会必败,所以双方都会避免走到最后一个格子上,那么就转化为看 $a_{r-1}$ 的奇偶性。
容易发现,区间中最后一个先手必胜的格子是 $\leq r$ 的最右边的奇数位置。然后,奇数位置 $i$ 上一个先手必胜的格子是 $\leq i-m-1$ 的最右边的奇数位置。依次类推。
那么,$a_l$ 是先手必胜位置,必须满足 $a_l$ 是奇数,且 $r$ 能通过上述方式恰好跳到位置 $l$。
令 $p_i$ 表示 $\leq i$ 的最右边的奇数位置。
我们把所有奇数结点拿出来,对于结点 $i$,从 $p_i$ 的最右边结点处向 $i$ 连边,表示可以转移。这样形成了一个树形结构。
那么,$r$ 能跳到 $l$,必须满足 $l$ 是 $p_r$ 的祖先。这个可以通过预处理 dfs 序以后 $O(1)$ 判断。
然后就可以 $O(n+q)$ 解决。
注意,我们上面讨论的先手,指的是先手到达这个位置并执行一次减 $1$ 操作。本题由于开始时先减去了 $1$,所以可以认为后手走到了这个点,相当于询问先手是否必败。要注意区别。
Code:
#include<iostream>
#include<vector>
using namespace std;
const int N=1e6+5;
int n,m,q,tp,A,B,C,P,pre[N],dfn[N],idx,sz[N];
inline int rnd(){return A=(A*B+C)%P;}
vector<int>G[N];
void dfs(int now){
dfn[now]=++idx,sz[now]=1;
for(int i=0;i<G[now].size();++i)
dfs(G[now][i]),sz[now]+=sz[G[now][i]];
}
inline bool islca(int x,int y){return dfn[x]<=dfn[y]&&dfn[y]<dfn[x]+sz[x];}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>m>>q>>tp;
for(int i=1;i<=n;++i){
int x;
cin>>x;
if(x&1)G[i-m-1>=0?pre[i-m-1]:0].push_back(pre[i]=i);else pre[i]=pre[i-1];
}
dfs(0);
unsigned ans=0;
if(tp)cin>>A>>B>>C>>P;
for(unsigned T=1;T<=q;++T){
int l,r;
if(tp)l=rnd()%n+1,r=rnd()%n+1;else cin>>l>>r;
if(l>r)l^=r^=l^=r;
if(pre[l]!=l||!islca(l,pre[r]))ans+=T*T;
}
cout<<ans<<'\n';
return 0;
}