cf1076G. Array Game
题解
考虑到每个点被跳到后都会 $-1$ ,所以我们一开始就把整个序列都 $-1$ ,然后玩法就变成要么跳到 $[i+1,\min(k,i+m)]$ 上的点,要么自身权值 $-1$ 。
然后显然可以直接转化为每个点为 $0$ 或 $1$ 。
所以考虑 $\text{dp}$ : $f_{i,0/1}$ 表示第 $i$ 个点为 $0/1$ ,先手是否能够必胜,考虑转移:
$$f_{i,0}=\text{or}\{f_{i+x,a_{i+x}} \text{xor}\ 1\}$$$$f_{i,1}=\text{or}\{f_{i+x,a_{i+x}} \text{xor}\ 1,f_{i,0}\text{xor}\ 1\}$$
因为 $m$ 很小,所以我们可以考虑线段树维护这个 $\text{dp}$ ,具体来说就是 $[l,r]$ 区间,维护 $f_S$ 表示 $r+1,r+2,...,r+m$ 的必胜状态为 $S$ 的情况下, $l,l+1,...,l+m-1$ 的必胜状态。于是可以 $O(2^m)$ 合并。
然后考虑区间加的操作,加偶数没影响,加奇数的话实际上就是把 $\text{dp}$ 值互换,所以只要再同时维护相反的 $\text{dp}$ 值即可。
然后我们可以发现其实我们只关心最近的必输状态的点离端点的距离,如果 $>m$ 了就记 $m+1$ ,因此就只要 $O(m)$ 合并即可。
效率: $O(nm\log n)$ 。
代码
#include <bits/stdc++.h> #define LL long long using namespace std; const int N=2e5+5; int n,m,q,a[N],tg[N<<2]; struct O{int s[7];}t[2][N<<2]; #define Ls k<<1 #define Rs k<<1|1 #define mid ((l+r)>>1) O hb(O u,O v){ O w; for (int i=1;i<=m+1;i++) w.s[i]=u.s[v.s[i]]; return w; } void up(int k){ t[0][k]=hb(t[0][Ls],t[0][Rs]); t[1][k]=hb(t[1][Ls],t[1][Rs]); } void build(int k,int l,int r){ if (l==r){ for (int i=1;i<=m;i++) t[a[l]][k].s[i]=t[!a[l]][k].s[i]=i+1; t[a[l]][k].s[m+1]=1; t[!a[l]][k].s[m+1]=m+1; return; } build(Ls,l,mid); build(Rs,mid+1,r);up(k); } void push(int k){ swap(t[0][k],t[1][k]);tg[k]^=1; } void down(int k){ push(Ls);push(Rs);tg[k]=0; } void upd(int k,int l,int r,int L,int R){ if (L<=l && r<=R) return push(k); if (tg[k]) down(k); if (mid>=L) upd(Ls,l,mid,L,R); if (mid<R) upd(Rs,mid+1,r,L,R); up(k); } O qry(int k,int l,int r,int L,int R){ if (L<=l && r<=R) return t[0][k]; if (tg[k]) down(k); if (mid>=R) return qry(Ls,l,mid,L,R); if (mid<L) return qry(Rs,mid+1,r,L,R); return hb(qry(Ls,l,mid,L,R),qry(Rs,mid+1,r,L,R)); } int main(){ scanf("%d%d%d",&n,&m,&q); for (int i=1;i<=n;i++){ LL v;scanf("%lld",&v); v--;a[i]=(v&1ll); } build(1,1,n); for (int op,l,r;q--;){ scanf("%d%d%d",&op,&l,&r); if (op&1){ LL v;scanf("%lld",&v); if (v&1ll) upd(1,1,n,l,r); } else{ O u=qry(1,1,n,l,r); printf("%d\n",1+(u.s[m+1]==1)); } } return 0; }