【CodeForces】445B A Lot of Games 字典树博弈

传送门:【CodeForces】445B  A Lot of Games


题目大意:两人一起构造一个串,每人每次向串的末尾放一个字母,必须保证放了这个字母后能够成所给的N个串的前缀,如果某个人不能放时,则另一个人赢。如果谁在第i局输了,那么他就在第i+1局先走。现在问如果要进行K次游戏且以第K次游戏作为最终的结果(第K次游戏赢就是全局赢,输就是全局输),如果两个人足够聪明且都想赢得游戏的胜利,问先手能赢还是后手能赢。


题目分析:首先我们构造一棵字典树,这个很简单,然后重点就是树上的博弈了。

我们需要分析各种胜利的情况:

1.先手必胜且先手可以败,比如aba,abbc就是这种情况,此时只要先手一开始一直输,最后赢即可。

2.先手必败,后手一直赢到结束,所以后手一定赢。

3.先手必胜且先手不能败,一条链就有可能如此,此时k为奇数则先手胜,否则后手胜。

那么怎么判断是否可以先手必胜呢?

假设每个节点表示的是先手取该节点先手是否可以胜,那么很显然边界条件就是叶子节点一定是必胜点,且因为是两个人博弈,所以如果某个节点的后继节点是必胜点,那么对手肯定会走那一个节点,所以该节点一定是必败点,否则该节点就是必胜点。最后,如果根结点是必败点,则说明先手走的第一步一定有一步是必胜点,所以先手必胜,否则先手必败。

既然我们已经知道了先手必胜的条件,那么我们可不可以找到先手可败的条件?

假设每个节点表示的是先手取该节点先手是否可败,那么很明显边界条件就是叶子节点一定是不可败点,且因为是两个人博弈,所以如果某个节点的后继节点是可败点,那么对手一定会选择走可败点,所以该节点是不可败点,否则该节点就是可败点。最后如果根结点是不可败点,则说明先手走的第一步里一定有一步是可败点,所以先手可败,否则先手不可败。

好了,现在我们需要的条件都已经得到了,那么我们就可以愉快的虐题了~

PS:得到这个结论倒是不容易啊。。。真正的AC了以后反而思路越加清晰了。。。一开始能AC真是不可思议啊~


代码如下:


#include <map>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;

#define REP( i , a , b ) for ( int i = a ; i < b ; ++ i )
#define FOR( i , a , b ) for ( int i = a ; i <= b ; ++ i )
#define REV( i , a , b ) for ( int i = a ; i >= b ; -- i )
#define CLR( a , x ) memset ( a , x , sizeof a )

const int MAXN = 100005 ;

int root , P ;
int next[MAXN][26] ;
char buf[MAXN] ;
int win[MAXN] , lose[MAXN] ;
int n , k ;

void insert ( char buf[] ) {
	int now = root ;
	for ( int i = 0 ; buf[i] ; ++ i ) {
		int x = buf[i] - 'a' ;
		if ( !next[now][x] )
			next[now][x] = ++ P ;
		now = next[now][x] ;
	}
}

void dfs ( int u ) {
	win[u] = 1 ;
	lose[u] = 0 ;
	int leaf = 1 ;
	REP ( i , 0 , 26 ) {
		int v = next[u][i] ;
		if ( !v )
			continue ;
		leaf = 0 ;
		dfs ( v ) ;
		if ( win[v] )
			win[u] = 0 ;
		if ( !lose[v] )
			lose[u] = 1 ;
	}
	if ( leaf )
		lose[u] = 1 ;
}

void solve () {
	P = root = 0 ;
	CLR ( next , 0 ) ;
	REP ( i , 0 , n ) {
		scanf ( "%s" , buf ) ;
		insert ( buf ) ;
	}
	dfs ( root ) ;
	int first_can_win = !win[root] ;
	int first_can_lose = lose[root] ;
	if ( !first_can_win )
		printf ( "Second\n" ) ;
	else if ( k == 1 || first_can_lose )
		printf ( "First\n" ) ;
	else
		printf ( "%s\n" , k & 1 ? "First" : "Second" ) ;
}

int main () {
	while ( ~scanf ( "%d%d" , &n , &k ) )
		solve () ;
	return 0 ;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值