题解/算法 {894. 拆分-Nim游戏}
@LOC: 1
LINK: https://www.acwing.com/problem/content/description/896/
;
首先题意有点模糊…
给定一个集合{x1, x2, x3} (xi > 0)
, 一次操作: 选择一个元素x2
, 选择两个数a,b
满足0 <= a,b < x2
(如果不存在这样的a,b
则失败), 然后新集合变成了 {x1, a, b, x3}
;
比如, {0}
是必败的, 而{>0}
都是必赢的 (因为可以变成{0,0}
);
可以看出是公平游戏, 证明他是 组合游戏: 对于局面{x1,x2,x3}
每次选择一个元素操作 不影响其他元素, 因此 每个元素 均是一个 独立的子游戏;
MARK: @LOC_0
;
因此Get_SG( int _a)
表示: 子游戏的局面是{a}
;
.
根据上面的分析, 其实 我们已经知道了他的胜败 (a=0
必败, 否则a>0
必胜), 但是 其实没什么用处…
因为我们要得到他的SG值 (理解这点很重要), 你不可以直接
O
(
1
)
O(1)
O(1)判断 然后必败返回个
0
0
0 必胜则返回个
1
1
1, 这是错误的! 因此 我们要求的是整个游戏的输赢 而你当前这个局面 只是他的一个子游戏的局面, 他的输赢 与 整个游戏的输赢无关; 除非这个{a}
局面 就是整个游戏的局面 那你可以这样做, 否则一定错误;
.
因此, 即便你知道了 子游戏的输赢状态, 没用 还是得求他的SG值;
这题还有一点特别, 通常 我们处理单独的子游戏 即Get_SG( _a)
, 那他所能衍生的所有局面 都是单独的子游戏, 可以这道题 他的子节点 会分裂成{a, b}
, 也就是 由一个子游戏 又变成了 一个 组合游戏;
因此这里还要再次使用SG定理, 即子节点{a,b}
的SG值 (一般子节点的SG值 是通过Get_SG
函数的返回值 获得的) 但这里 由于子节点不是单独的子游戏 而是一个组合游戏, 所以再次根据SG定理 (即组合游戏的SG值 等于各个子游戏的SG值的异或和), 得到 子节点的SG值为Get_SG(a) ^ Get_SG( b)
;
@DELI;
int Get_SG( int _a){ // `a >= 0`;
static int __record[ 102];
{ static bool __is_first = true; if( __is_first){ __is_first = false; memset( __record, -1, sizeof( __record));}}
auto & ANS = __record[ _a];
if( ANS != -1){ return ANS;}
//--
{ // 检查终止态;
bool is_leaf = false;
if( _a == 0){
is_leaf = true;
}
if( is_leaf){ return ANS = 0;}
}
unordered_set< int> mex;
for( int a = 0; a < _a; ++a){
for( int b = a; b < _a; ++b){
auto aa = Get_SG( a);
auto bb = Get_SG( b);
mex.insert( aa ^ bb);
}
}
//> 对`mex`进行MEX操作;
for( ANS = 0; ; ++ANS){
if( mex.find( ANS) == mex.end()){
break;
}
}
ASSERT_( ANS > 0);
return ANS;
}
void __Solve(){
cin>> N;
int SG = 0;
for( int i = 0; i < N; ++i){
int a; cin>> a;
SG ^= Get_SG( a);
}
if( SG == 0){
cout<< "No"<< ED_;
}
else{
cout<< "Yes"<< ED_;
}
}