uoj #266. 【清华集训2016】Alice和Bob又在玩游戏

链接

http://uoj.ac/problem/266
中文题

题解

先考虑 O ( n 2 ) O(n^2) O(n2)的做法
对于一个子树,如果我们可以知道他可以凑出哪些数
那么对于一个儿子 s o n j son_j sonj并到父亲的时候,就要异或所有别的儿子的SG值
如果暴力维护和vector什么的,就可以 n 2 n^2 n2

考虑怎么优化这个过程
一开始想维护线段树,但是似乎没什么办法资瓷合并
虽然现在想一想,如果把线段树的范围变成 [ 0 , 2 i ] [0,2^i] [0,2i]就可以了
也可以使用字典树
和线段树一样,都是把我能弄出来的数查到里面
那么打lazy的时候,就相当于是把某些位的左右儿子取反
显然可以实现
然后求sg的时候,就再字典树上面二分就可以了

CODE:

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
typedef long long LL;
const int N=100005;
int T; 
int n,m;
vector<int> vec[N];
bool vis[N];
int sg[N];
int bin[21];
int rt[N],s1[N*22],s2[N*22],lzy[N*22],c[N*22];
int num=0;
void Push_down (int now,int dep)
{
	if (lzy[now]&bin[dep-1])	swap(s1[now],s2[now]);
	lzy[s1[now]]^=lzy[now];lzy[s2[now]]^=lzy[now];
	lzy[now]=0;
}
void Merge (int &rt1,int rt2,int dep)
{
	if (rt1==0)	{rt1=rt2;return ;}
	if (rt2==0) return ;
	Push_down(rt1,dep);	Push_down(rt2,dep);
	Merge(s1[rt1],s1[rt2],dep-1);Merge(s2[rt1],s2[rt2],dep-1);
	c[rt1]=c[s1[rt1]]+c[s2[rt1]]+(dep==0);
}
void insert (int &now,int x,int dep)
{
	if (now==0) now=++num;c[now]++;
	if (dep<=0) return ;
	if (x&bin[dep-1]) insert(s2[now],x,dep-1);
	else insert(s1[now],x,dep-1);
}
void dfs (int x,int fa)
{
	vis[x]=true;
	int siz=vec[x].size();int t=0;
	for (int u=0;u<siz;u++)
	{
		int y=vec[x][u];
		if (y==fa) continue;
		dfs(y,x);
		t^=sg[y];
	}
	insert(rt[x],t,21);
	for (int u=0;u<siz;u++)
	{
		int y=vec[x][u];
		if (y==fa) continue;
		lzy[rt[y]]^=(t^sg[y]);
		Merge(rt[x],rt[y],21);
	}
	int now=rt[x];sg[x]=0;
	for (int u=21;u>0;u--)
	{
		//printf("%d %d %d %d %d\n",now,c[now],u,s1[now],c[s1[now]]);
		if (c[s1[now]]<bin[u-1]) now=s1[now];
		else	{sg[x]|=bin[u-1];now=s2[now];}
	}
//	printf("%d %d\n",x,sg[x]);
}
int main()
{
	bin[0]=1;for (int u=1;u<=20;u++) bin[u]=(bin[u-1]<<1);
	scanf("%d",&T);
	while (T--)
	{
		scanf("%d%d",&n,&m);
		for (int u=1;u<=n;u++) vis[u]=false,vec[u].clear(),rt[u]=0;
		for (int u=1;u<=m;u++)
		{
			int x,y;
			scanf("%d%d",&x,&y);
			vec[x].push_back(y);vec[y].push_back(x);
		}
		int ans=0;
		for (int u=1;u<=n;u++) if (vis[u]==false)	{dfs(u,0);ans^=sg[u];}
		
		for (int u=0;u<=num;u++)	s1[u]=s2[u]=c[u]=lzy[u]=0;num=0;
		
		if (ans!=0) printf("Alice\n");
		else printf("Bob\n");
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值