1. 程式人生 > >【雜題】[CodeForces 1076G] Array Game【資料結構】【博弈】

【雜題】[CodeForces 1076G] Array Game【資料結構】【博弈】

原題連結:http://codeforces.com/problemset/problem/1076/G

Description

考慮這樣一個博弈
你有一個序列B,一開始有一個棋子在B的第一個位置。
雙方輪流操作,第一次操作前將B[1]-1
遊戲有一個引數m,操作以下面的形式進行

  • 假設當前棋子在位置x,當前操作的一方需要選擇一個位置 y [ x
    , x + m ] y\in[x,x+m]
    ,且B[y]>0,將棋子移到位置y,並將B[y]-1,之後將操作權給另一個人。
  • 不能操作的一方輸掉遊戲。

這道題目是這樣的
給出一個長度為n的序列A,遊戲引數m,以及詢問數q
詢問有兩種,一種是將A的一段區間加上d,另一種是詢問A序列的一個子區間,以這段區間作為序列B進行上面的博弈,問最優策略下先手還是後手會贏。

n , q 200000 , m 5 ,

1 A [ i ] , d 1 0 12 n,q\leq 200000,m\leq 5,1\leq A[i],d\leq 10^{12}

Solution

思考這個博弈的性質。

因為是兩個人輪流操作,每次-1,那麼可以往奇偶性的方向來思考。

考慮一個B[i]為偶數,玩家b從前面將棋子移到了這裡,現在B[i]為奇數,玩家a操作。

對於每個位置i,我們可以給它定一個0/1狀態,表示棋子從前面第一次移到i這裡,減1以後開始操作的玩家必敗還是必勝,記為F[i]。

考慮 F [ i + 1 ] F[i+1] 以後都求出來了,我們想要知道 F [ i ] F[i]

如果 B [ i ] B[i] 為偶數,即棋子第一次移過來時B[i]變成奇數,考慮此時先手(令他為玩家a)的選擇。

  • 如果 F [ i + 1 ] . . . F [ i + m ] F[i+1]...F[i+m] 中至少有一個為0(即先手必敗態),此時玩家a肯定走過去將操作權交給玩家b,玩家b必敗,玩家a必勝。
  • 否則[i+1,i+m]全是先手必勝態,玩家a會原地不動,將B[i]-1,由於第一次-1後 B [ i ] B[i] 是奇數,最後必須向後走的玩家一定是玩家b,將先手必勝態留給玩家a,此時玩家a必勝。

如果 B [ i ] B[i] 為奇數,即棋子第一次移過來時B[i]變成偶數。此時玩家a操作

  • 如果 F [ i + 1 ] . . . F [ i + m ] F[i+1]...F[i+m] 中至少有一個為0(即先手必敗態),此時玩家a肯定走過去將操作權交給玩家b,玩家b必敗,玩家a必勝。
  • 否則[i+1,i+m]全是先手必勝態,最後必須向後走的玩家一定是玩家a,將先手必勝態留給玩家b,此時玩家a必敗。

綜上,若B[i]為偶數,則 F [ i ] = 1 F[i]=1
若B[i]為奇數,則如果 [ i + 1 , i + m ] [i+1,i+m] 中存在 F [ j ] = 0 F[j]=0 ,那麼 F [ i ] = 1 F[i]=1 ,否則 F [ i ] = 0 F[i]=0

因此 F [ i ] F[i] 只與 B [ i ] B[i] 的奇偶性和 F [ i + 1 ] . . . F [ i + m ] F[i+1]...F[i+m] 有關。

回到原問題,如何處理區間詢問呢?
我們可以用二進位制狀態將 F [ i + 1 ] . . . F [ i + m ] F[i+1]...F[i+m] 壓起來,有奇偶兩種轉移。

用線段樹維護每個區間的總轉移,合併兩個區間就直接合並轉移即可。
這樣查詢就解決了。

考慮如何區間加法
區間加偶數顯然沒用,區間加奇數相當於奇偶調換,我們不好維護調換後的轉移怎麼辦。
可以直接將調換後的轉移存起來,因為調換兩遍相當於沒調換,對於每個線段樹區間維護 G [ 0 / 1 ] [ S ] G[0/1][S] 表示區間加上0/1後的轉移,區間加奇數直接將這兩個swap一下就好了。

總的複雜度 O ( n log n 2 m ) O(n\log n*2^m)

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");
		}
	}
}