【杂题】[CodeForces 1076G] Array Game【数据结构】【博弈】

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hzj1054689699/article/details/84567322

原题链接: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");
		}
	}
}

猜你喜欢

转载自blog.csdn.net/hzj1054689699/article/details/84567322
今日推荐