P3179 [HAOI2015]数组游戏 [* medium]
给定 (n) 个硬币,多次查询,每次给定 (w) 表示有 (w) 枚硬币正面朝上(具体位置给定),每次操作为:
- 选择一面正面朝上的硬币并翻转,然后假设其下标为 (x),此时你可以翻转 (x,2x...kx(kxle n))
求先手后手谁会 win,(nle 10^9,q,wle 100)
Solution
首先有一个我不会的结论,但是论证并不是很难,若干枚硬币组成的游戏的 SG 值就是各个硬币的 SG 值的异或和。
论证的话考虑翻转一枚硬币并当作子结论处理,由于翻硬币中消去一个元素与对游戏的 SG 值异或上相同的 SG 值等价,所以不难看出 SG 定理的成立。
知道这个后这个题就很 easy 了。
不难注意到,对于 (x) 这枚硬币而言,我们计算的 SG 值等价于将 (x) 视为 (1) 号硬币,然后考虑总共有 (lfloorfrac{n}{x} floor) 枚硬币下的情况。
考虑对于某个 (lfloorfrac{n}{x} floor) 如何快速的计算答案,等价于将此情况下 (2,3...m) 的 SG 值异或起来的 ( m mex)
由于结论仍然是生效的,此时只需要考虑 (frac{n}{xt}) 的形式,从小到大枚举 (frac{n}{xt}) 并根据奇偶性判断能否对后续产生影响(加入肯定是可以加入的),对于每次我们计算一次 ( m mex),不难注意到 ( m mex) 的上界是 (sqrt{frac{n}{x}}) 的级别,所以直接暴力找 mex 就可以了。
于是我们可以在 (mathcal O(n^{frac{3}{4}})) 的时间复杂度解决这个问题。
- (sum sqrt{frac{n}{x}}le int sqrt{x}+int sqrt{frac{n}{x}}=n^{frac{3}{4}})
(Code:)
#include<bits/stdc++.h>
using namespace std ;
#define Next( i, x ) for( register int i = head[x]; i; i = e[i].next )
#define rep( i, s, t ) for( register int i = (s); i <= (t); ++ i )
#define drep( i, s, t ) for( register int i = (t); i >= (s); -- i )
#define re register
int gi() {
char cc = getchar() ; int cn = 0, flus = 1 ;
while( cc < '0' || cc > '9' ) { if( cc == '-' ) flus = - flus ; cc = getchar() ; }
while( cc >= '0' && cc <= '9' ) cn = cn * 10 + cc - '0', cc = getchar() ;
return cn * flus ;
}
const int N = 1e5 + 5 ;
int n, cnt, top, num, v1[N], v2[N], sg[N], f[N], g[N], mx[N] ;
struct node {
int len, w ;
node(int _len = 0, int _w = 0) { len = _len, w = _w ; }
} st[N] ;
int Get(int x) { return (x <= cnt) ? f[x] : g[n / x] ; }
void add(int x) { mx[x] = 1, sg[++ num] = x ; }
void dec(int x) { mx[x] = 0 ; }
void dfs(int x) {
if( x <= cnt && v1[x] ) return ;
if( x > cnt && v2[n / x] ) return ;
(x <= cnt) ? v1[x] = 1 : v2[n / x] = 1 ;
for(re int l = 2, r; l <= x; l = r + 1)
r = x / (x / l), dfs(x / l) ;
top = 0 ;
for(re int l = 2, r; l <= x; l = r + 1)
r = x / (x / l), st[++ top] = node((r - l + 1), Get(x / l)) ;
int sf = 0, u = 1 ;
for(re int j = 1; j <= top; ++ j) {
add(sf ^ st[j].w) ;
if( st[j].len & 1 ) sf ^= st[j].w ;
}
while( mx[u] ) ++ u ;
(x <= cnt) ? f[x] = u : g[n / x] = u ;
while( num ) dec(sg[num]), -- num ;
}
signed main()
{
n = gi(), cnt = sqrt(n) ;
f[1] = 1, v1[1] = 1, dfs(n) ;
int q = gi() ;
while( q-- ) {
int w = gi(), SG = 0, x ;
while( w-- ) x = gi(), SG ^= Get(n / x) ;
( !SG ) ? puts("No") : puts("Yes") ;
}
return 0 ;
}