【雜題】[CodeForces 1076G] Array Game【資料結構】【博弈】
原題連結:http://codeforces.com/problemset/problem/1076/G
Description
考慮這樣一個博弈
你有一個序列B,一開始有一個棋子在B的第一個位置。
雙方輪流操作,第一次操作前將B[1]-1
遊戲有一個引數m,操作以下面的形式進行
- 假設當前棋子在位置x,當前操作的一方需要選擇一個位置 ,且B[y]>0,將棋子移到位置y,並將B[y]-1,之後將操作權給另一個人。
- 不能操作的一方輸掉遊戲。
這道題目是這樣的
給出一個長度為n的序列A,遊戲引數m,以及詢問數q
詢問有兩種,一種是將A的一段區間加上d,另一種是詢問A序列的一個子區間,以這段區間作為序列B進行上面的博弈,問最優策略下先手還是後手會贏。
Solution
思考這個博弈的性質。
因為是兩個人輪流操作,每次-1,那麼可以往奇偶性的方向來思考。
考慮一個B[i]為偶數,玩家b從前面將棋子移到了這裡,現在B[i]為奇數,玩家a操作。
對於每個位置i,我們可以給它定一個0/1狀態,表示棋子從前面第一次移到i這裡,減1以後開始操作的玩家必敗還是必勝,記為F[i]。
考慮 以後都求出來了,我們想要知道
如果 為偶數,即棋子第一次移過來時B[i]變成奇數,考慮此時先手(令他為玩家a)的選擇。
- 如果 中至少有一個為0(即先手必敗態),此時玩家a肯定走過去將操作權交給玩家b,玩家b必敗,玩家a必勝。
- 否則[i+1,i+m]全是先手必勝態,玩家a會原地不動,將B[i]-1,由於第一次-1後 是奇數,最後必須向後走的玩家一定是玩家b,將先手必勝態留給玩家a,此時玩家a必勝。
如果 為奇數,即棋子第一次移過來時B[i]變成偶數。此時玩家a操作
- 如果 中至少有一個為0(即先手必敗態),此時玩家a肯定走過去將操作權交給玩家b,玩家b必敗,玩家a必勝。
- 否則[i+1,i+m]全是先手必勝態,最後必須向後走的玩家一定是玩家a,將先手必勝態留給玩家b,此時玩家a必敗。
綜上,若B[i]為偶數,則
若B[i]為奇數,則如果
中存在
,那麼
,否則
因此 只與 的奇偶性和 有關。
回到原問題,如何處理區間詢問呢?
我們可以用二進位制狀態將
壓起來,有奇偶兩種轉移。
用線段樹維護每個區間的總轉移,合併兩個區間就直接合並轉移即可。
這樣查詢就解決了。
考慮如何區間加法
區間加偶數顯然沒用,區間加奇數相當於奇偶調換,我們不好維護調換後的轉移怎麼辦。
可以直接將調換後的轉移存起來,因為調換兩遍相當於沒調換,對於每個線段樹區間維護
表示區間加上0/1後的轉移,區間加奇數直接將這兩個swap一下就好了。
總的複雜度
Code
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <cstring>
#define fo(i,a,b) for(int i=a;i<=b;++i)
#define fod(i,a,b) for(int i=a;i>=b;--i)
#define N 400005
#define LL long long
using namespace std;
int lz[N],f[N][2][32],n1,t[N][2],a[N],n,m,q,d[N];
void up(int k)
{
int l=1<<m;
fo(p,0,1) fo(j,0,l-1) f[k][p][j]=f[t[k][0]][p][f[t[k][1]][p][j]];
}
void upd(int k)
{
lz[k]^=1,swap(f[k][0],f[k][1]);
}
void down(int k)
{
if(lz[k]) upd(t[k][0]),upd(t[k][1]),lz[k]=0;
}
void add(int k,int l,int r,int x,int y)
{
if(x>y||x>r||y<l) return;
if(x<=l&&r<=y) upd(k);
else
{
int mid=(l+r)>>1;
down(k);
add(t[k][0],l,mid,x,y),add(t[k][1],mid+1,r,x,y);
up(k);
}
}
void query(int k,int l,int r,int x,int y)
{
if(x>y||x>r||y<l) return;
if(x<=l&&r<=y) d[++d[0]]=k;
else
{
int mid=(l+r)>>1;
down(k);
query(t[k][0],l,mid,x,y),query(t[k][1],mid+1,r,x,y);
}
}
void build(int k,int l,int r)
{
if(l==r)
{
int le=(1<<m);
fo(j,0,le-1)
{
f[k][a[l]][j]=(j<<1)%le+1;
f[k][a[l]^1][j]=(j<le-1)?((j<<1)%le+1):(j<<1)%le;
}
return;
}
int mid=(l+r)>>1;
build(t[k][0]=++n1,l,mid);
build(t[k][1]=++n1,mid+1,r);
up(k);
}
int main()
{
cin>>n>>m>>q;
fo(i,1,n)
{
LL x;
scanf("%lld",&x);
a[i]=x%2;
}
n1=1;
build(1,1,n);
fo(i,1,q)
{
int p,x,y;
LL z;
scanf("%d%d%d",&p,&x,&y);
if(p==1)
{
scanf("%lld",&z);
if(z%2) add(1,1,n,x,y);
}
else
{
d[0]=0;
query(1,1,n,x,y);
int s=(1<<m)-1;
fod(i,d[0],1) s=f[d[i]][0][s];
if(s%2==0) printf("2\n");
else printf("1\n");
}
}
}