【洛谷P5652】基础博弈练习题【dfs】【博弈论】

题目:

题目链接:https://www.luogu.org/problem/P5652
YSGH和YGSH在打膈膜,YSGS在旁边围观。

规则是这样的,先给定一个正整数mm和一个nn个数序列BB,一开始有一个棋子在BB的第一个位置,并将B1B_1减去11。此后双方轮流操作,每次操作,假设当前棋子在ii,可以把棋子移到一个位置jj,满足j[i,min(i+m,n)]j\in[i,min(i+m,n)]Bj>0B_j>0,然后将BjB_j11,YSGH先手,谁先不能操作谁输。

众所周知,YSGH和YGSH都是绝顶聪明的,所以两人都会使用最优策略。

而隔膜使用的序列BB是一个序列AA的一个连续非空子序列,当然序列AA和每次隔膜使用的序列BB都是YSGS定的。

现在他们进行了qq轮游戏,给出每轮游戏使用的区间,请你判断每轮谁会赢。


思路:

如果位置ii先手是必败的,那么[im,i1][i-m,i-1]先手都只要跳到ii,就使得当前选择的后手必败。也就是先手必胜。
那么如果[i+1,i+m][i+1,i+m]均为先手必胜,那么当选择到ii时,如果ii为奇数,那么最终就是先手取完这堆石子,那么必败的石子堆时后手先取,所以先手必胜。反之,如果为偶数,先手必败。
所以我们维护出每一个位置iinxt[i]nxt[i],表示满足aja_j为奇数且j<ij<ijj尽量大的jj
所以如果ii必胜,那么nxt[im1]nxt[i-m-1]也必胜。所以如果我们从nxt[im1]nxt[i-m-1]ii连边,那么区间[l,r][l,r]如果满足llnxt[r]nxt[r]的祖先,那么就先手必败,否则先手必胜。
判断是否为祖先节点可以用dfsdfs序来O(1)O(1)解决。


代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;

const int N=1000010;
const ll MOD=(1LL<<32);
int n,m,Q,l,r,type,tot,a[N],head[N],nxt[N],dfn[N],size[N];
ll ans;

struct edge
{
	int next,to;
}e[N];

int A,B,C,P;
inline int rnd(){return A=(A*B+C)%P;}

void add(int from,int to)
{
	e[++tot].to=to;
	e[tot].next=head[from];
	head[from]=tot;
}

void dfs(int x)
{
	dfn[x]=++tot; size[x]=1;
	for (int i=head[x];~i;i=e[i].next)
	{
		int v=e[i].to;
		dfs(v);
		size[x]+=size[v];
	}
}

int main()
{
	memset(head,-1,sizeof(head));
	scanf("%d%d%d%d",&n,&m,&Q,&type);
	for (int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		if (a[i]&1) nxt[i]=i;
			else nxt[i]=nxt[i-1];
		if (i-m-1>0) add(nxt[i-m-1],i);
			else add(0,i);
	}
	tot=0;
	dfs(0);
	if (type) scanf("%d%d%d%d",&A,&B,&C,&P);
	for (int i=1;i<=Q;i++)
	{
		if (type)
		{
			l=rnd()%n+1,r=rnd()%n+1;
			if (l>r) swap(l,r);
		}
		else scanf("%d%d",&l,&r);
		if (!(dfn[l]<=dfn[nxt[r]] && dfn[l]+size[l]>=dfn[nxt[r]]+size[nxt[r]]))
			ans=(ans+1LL*i*i)%MOD; 
	}
	printf("%lld",ans);
	return 0;
}
posted @ 2019-11-13 19:59  全OI最菜  阅读(243)  评论(0编辑  收藏  举报