放石子’s 题解

互讲题目阶段开始。。。
来自信息学奥赛一本通T1815
(其实本来我组是要搞一二两章来着,后来换了一下。。。)
开始博弈论进阶???
emmm,其实我基础博弈都不是特别好的啦。。。
语言表达能力较差,接下来讲题可能会有bug什么的,请大家见谅。
进入正题前再吐槽一句,这是我第一次用bitset没想到是为了博弈论而去学习的。。。
先来几个有关链接:
sg函数介绍
bitset介绍
bitset的内置函数
线性基介绍
那切正题:
题目描述:
给一张 n n n 个点, m m m 条边的有向无环图,每条边有颜色 c c c,在图上放了 q q q 颗石子,每颗石子在一个点上。每次操作时选择一个有出边且点上有石子的点 x x x,从点上取走一颗石子,然后选择一个颜色集合 S S S,对于每条( x x x 的)满足颜色 e ∈ S e\in S eS 的出边 i i i,在边 i i i 的终点上放上一颗石子。双方轮流操作,不能操作者负。问先手赢还是后手赢。
说明一下题面描述里面的括号是原题面没有的我自己加上去的,因为我认为题面出锅了,只有这样解释才能做,否则没有办法做。
呃,这个游戏是公平的,所以可以考虑博弈论。
考虑sg函数(博弈论基本套路)。
将每个节点看成一个状态,初始情况显然出度为0的点为必败状态。
首先每个石子的都是独立的,那我们对于每个点只要考虑此节点上有石子时的sg函数即可,然后把所有石子对应的sg函数异或起来即可(博弈论基本操作)。
因为要找出度为0的点开始推,那我们就可以先来一手逆拓扑排序。
开个 e x [ i ] [ j ] ex[i][j] ex[i][j] 表示 i i i 号点的出边颜色为 j j j 的sg,开个 s t p stp stp 数组便是当前对颜色的线性相关性(0为无关1为有关),如果 e x ex ex 不为0(当前点与当前颜色有关系),那么就可以把它插入线性基(操作在介绍中有)。在之后枚举中找到最小的线性有关位,它即是对答案的影响,在sg函数中标记出来(没有则不用标记)。
在扫描边的时候记得把 e x ex ex 数组异或上当前点的sg函数。
最后判断答案再输出即可。
时间复杂度证明:
求sg函数最坏的时间复杂度为 O ( n ) O(n) O(n),而你需要bitset来维护,那么最差的时间复杂度就是 O ( m ∗ n 2 64 ) O(\frac {m*n^2} {64}) O(64mn2)
code:

#include 
using namespace std;
const int N=200+5,M=5e3+10;
int n,m,q,a,ans=0,top[N],din[N],mac=0;
bool vis[N],stp[N];
struct lol{int x,y,z;} e[M];
queue <int> que;
bitset <N> sum,k[N],sg[N],ex[N][M];
//sum为最后答案,k为线性基,sg函数,ex[i][j]表示i号点的出边颜色为j的sg 
void ein(int x,int y,int z){
	e[++ans]=(lol){top[x],y,z};
	top[x]=ans;
}
void init(){
	scanf("%d%d",&n,&m);
	for(int i=0,s,t,c;++i<=m;
		scanf("%d%d%d",&s,&t,&c),ein(t,s,c),++din[s],mac=max(mac,c));//建反图 
}
int cr(int i){
	vis[i]=1;
	que.push(i);
}
int inst(bitset <N> now){//将now插入线性基  
	for(int i=n+1;--i>=0;)
		if(now[i])//与当前颜色有关 
			if(!stp[i]){//原本无关则插入并标记为有关 
				k[i]=now;//now此时已经为最小的线性基,赋值给k[i] 
				stp[i]=1; 
				return 0;
			}
			else now^=k[i];//原本有关则异或上形成最小线性基 
}
void tpst(){
	for(int i=0;++i<=n;din[i]?:cr(i));//(原图)没有出边即为必败状态 
	for(int x;!que.empty();){//topsort 
		x=que.front();
		que.pop();
		if(!vis[x]){//如果当前点不是无出边的点 
			memset(stp,0,sizeof(stp));//stp[i]表示当前点对颜色i的线性相关性 
			for(int i=-1;++i<=mac;!ex[x][i].any()?:inst(ex[x][i]));
			//如果当前点和枚举的颜色有关系,插入线性基 
			for(int i=-1;++i<=n;)
				if(!stp[i]){//找到最小的线性无关位
					sg[x][i]=1;//在sg函数中标记(这个是最小的线性基)
					break ;
				}
		}
		for(int i=top[x],y,z;i;i=e[i].x){//枚举接下来的边 
			y=e[i].y;
			z=e[i].z;
			ex[y][z]^=sg[x];//接下来ex异或上当前点的sg函数(因为会传到) 
			if(!(--din[y])) que.push(y);
		}
	}
}
void work(){
	tpst();
}
void prin(){
	scanf("%d",&q);
	for(int i=0;++i<=q;scanf("%d",&a),sum^=sg[a]);
	//求对每块石头的sg函数的异或和即为答案 
	printf("%d",sum.any()?1:0);
}
int main(){
	init();
	work();
	prin();
	return 0;
} 

真丑

感谢各位dalao捧场。

你可能感兴趣的:(题解,博弈论,线性基)