BZOJ 3576: [Hnoi2014]江南乐 (SG函数)

12 篇文章 0 订阅

题意

n n n堆石子,给定 F F F,每次操作可以把一堆石子数不小于 F F F的石子平均分配成若干堆(堆数 > 1 >1 >1).
平均分配即指分出来的石子数中最大值减最小值不超过 1 1 1.不能进行操作就算输.询问先手是否有必胜策略.

分析

因为每一推石子实际上是独立的.于是就可以求出每一堆石子的 S G SG SG函数后再异或起来.

于是看看怎么求 S G ( N ) SG(N) SG(N).方法是枚举分成的堆数 i i i.因为最大 − - 最小 &lt; = 1 &lt;=1 <=1,那么只可能有两种取值.设:

  • 分出来的较小值 r r r, 较大值就是 r + 1 r+1 r+1
    分出来的较大值的堆数为 k k k, 较小值的堆数就是 i − k i-k ik

那么有 r = N / i r=N/i r=N/i, k = N % i k=N\%i k=N%i. 那么只要把 S G ( r ) SG(r) SG(r)异或 ( i − k ) (i-k) (ik)次,把 S G ( r + 1 ) SG(r+1) SG(r+1)异或 k k k次.就能得到一个后继状态的 S G 值 SG值 SG.所以把所有的算出来的值取 m e x mex mex就得到了 S G ( N ) SG(N) SG(N).这里 m e x mex mex指没有出现过的最小非负整数值.因为异或偶数次相当于没有异或,所以判断一下 k k k或者 ( i − k ) (i-k) (ik)是否为奇数然后异或一次就行了.

但是这样的时间复杂度是 O ( N 2 ) O(N^2) O(N2)的,所以我们需要优化.

优化方法类似于莫比乌斯反演中所用到的整除分块.我们对于 r r r值相等的 i i i一起处理.假设我们在处理一个区间 [ i , j ] [i,j] [i,j],满足 r i = r i + 1 = . . . = r j = r r_i=r_{i+1}=...=r_j=r ri=ri+1=...=rj=r.那么从 k i k_i ki k j k_j kj一定是每一次减少 r r r,因为相当于在 k k k个较大值中选出 r r r个,每一个取走 1 1 1,组成一堆新的石子.
这个时候我们可以发现 k i k_i ki的奇偶性一定与 k i + 2 k_{i+2} ki+2相同,且 ( i − k i ) (i-k_i) (iki)的奇偶性也与 ( i + 2 − k i + 2 ) (i+2-k_{i+2}) (i+2ki+2)相同,所以说分成 i i i堆和分成 i + 2 i+2 i+2推石子得到的 S G SG SG值是一样的.那么 [ i + 2 , j ] [i+2,j] [i+2,j]的值都对 m e x mex mex没有贡献,所以我们只需要算 i i i i + 1 i+1 i+1的值就行了.那么时间复杂度就被降到了 O ( n n ) O(n\sqrt n) O(nn ).

CODE

#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
namespace READ {
	inline void read(int &num) {
		char ch; while((ch=getchar())<'0'||ch>'9');
		for(num=0;ch>='0'&&ch<='9';num=num*10+ch-'0',ch=getchar());
	}
}
using namespace READ;
const int MAXN = 100005;
int T, F, SG[MAXN], vis[MAXN];
inline int sg(int N) { //记忆化搜索,预处理是卡着时限过的,而记忆化搜索会快很多
	if(~SG[N]) return SG[N];
	if(N < F) return SG[N]=0;
	int now, r, k;
	for(int i = 2, j; i <= N; i = j+1) { //先把要用到的sg求出来,否则vis数组可能会被重复标号出错
		j = N/(N/i);                     //(但貌似这道题不会WA)
		r = N/i, k = N%i;
		if(k&1) sg(r+1);
		if((i-k)&1) sg(r);
		if(j > i) {
			r = N/(i+1), k = N%(i+1);
			if(k&1) sg(r+1);
			if(((i+1)-k)&1) sg(r);
		}
	}
	for(int i = 2, j; i <= N; i = j+1) {
        j = N/(N/i);
        r = N/i, k = N%i;
        now = 0;
        if(k&1) now ^= SG[r+1];
        if((i-k)&1) now ^= SG[r];
        vis[now] = N;
        if(j > i) { //可能区间长度只有1
            r = N/(i+1), k = N%(i+1);
            now = 0;
            if(k&1) now ^= SG[r+1];
            if(((i+1)-k)&1) now ^= SG[r];
            vis[now] = N;
        }
    }
	int mex = 0;
	for(; vis[mex] == N; ++mex);
	return SG[N]=mex;
}
int main () {
	memset(SG, -1, sizeof SG);
	read(T), read(F);
	while(T--) {
		int ans = 0, x, n; read(n); 
		while(n--)
			read(x), ans ^= sg(x);
		putchar(ans ? '1' : '0');
		if(T) putchar(' '); //注意格式
	}
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值