@NOI模拟2017.06.30


@description@

JOHNKRAM 和 C_SUNSHINE 在玩一个游戏。

游戏规则如下:有若干堆石子,游戏前选定一个正整数 p,JOHNKRAM 先手,两个人轮流操作。定义一次操作是选择某一堆石子,然后拿出其中的 p^k(k∈N) 个石子扔掉,不能操作者输。

C_SUNSHINE 表示判定谁能赢太简单了,于是他放了 n 堆石子,编号为 1∼n。
他每次把编号在某个区间内的石子堆加上若干个石子,或者询问以编号在某个区间内的石子堆进行游戏,是谁胜利。

JOHNKRAM 表示他不会做,于是他来向你求助。

input
第一行三个数 n, q, p,n 表示序列的长度,q 表示接下来操作的次数,p 的意义如题目描述中所说。
接下来一行 n 个数,第 i 个数表示初始时第 i 堆石子的石子数量。
接下来 q 行每行第一个数t表示操作类型,t=0 表示修改,t=1 表示询问。
对于一个修改操作,该行还会有三个数 l, r, x,表示把 [l…r] 的所有石子堆加上 x 个石子(博主注:x 为正数且属于 int 范围)。
对于一个询问操作,该行还会有两个数 l, r,表示询问以 [l…r] 的所有石子堆进行游戏是谁胜。

保证 1 <= n, q, p, 每堆石子的初始数量 <= 10^5。

output
对于每一个询问,如果 JOHNKRAM 胜利输出 1,否则输出 0。

sample input
10 9 3
2 6 2 5 8 7 4 3 4 1
1 1 10
0 5 7 15
1 1 3
0 3 9 11
1 3 7
0 4 5 53
0 1 2 26
1 6 10
1 4
sample output
0
0
0
1
0

@solution@

@part - 1@

通过 对sg函数打表 简单推导可得:

(1)当 p 为奇数时,sg函数循环节长度为 2,循环节为 01。即:
当 x % 2 == 0 时,sg[x] = 0。
当 x % 2 == 1 时,sg[x] = 1。

(2)当 p 为偶数时,sg函数循环节长度为 (p+1),循环节为 010101……01012。即:
当 x % (p+1) == p 时,sg[x] = 2。
当 (x % (p+1)) % 2 == 1 时,sg[x] = 1。
当 x % (p+1) != p 且 (x % (p+1)) % 2 == 0 时,sg[x] = 0。

简要(感性地)证明一下吧:
首先我们可以发现,当 x < p 时,只能从 sg[x-1] 到 sg[x],故一定形如 010101……
当 p 为奇数时,p^k 肯定也为奇数,即我们总是只能从偶数转移到奇数,奇数转移到偶数,故只能是 010101……
当 p 为偶数时,可以发现 p^k = (-1)^k (mod p+1),于是我们可以在 mod p+1 的意义下从 x+1 与 x-1 转移到 x。故当 x % (p+1) == p 时,sg[x] 只能等于 2。
于是可以得证。

@part - 2@

根据 sg 函数应用于博弈论的结论,我们必须在询问时求出区间 [l, r] 中 sg 函数的异或和才能判定胜负。
奇数很好办,写一个支持区间反转,统计区间中 1 的个数的线段树即可。

考虑偶数。我们需要在支持模意义下区间加操作时,至少可以询问一个区间内值等于 p 的个数(即 sg[x] = 2 的个数)。
我不知道线段树能不能搞。这显然是分块来搞。修改时整块加法标记,散块暴力重构;查询时整块通过加法标记寻找,散块暴力遍历。
再考虑求出一个区间内值为奇数的个数(即 sg[x] = 1 的个数)。考虑重构时储存值为奇数与值为偶数的两个有序数列,这样整块在加法标记下,新奇数序列必然是原奇数序列的某后缀接上原偶数序列的某前缀,或反过来原偶数序列的某后缀接上原奇数序列的某前缀。新偶数序列同理。
于是我们根据加法标记二分查找一下,就可以求出 sg[x] = 1 的个数了。

显然奇数也可以分块来搞,所以我们就可以不用写线段树了。

总复杂度 O(nsqrt(n)log n),有些卡但开了O2大概3s能过

标算给出了一个 O(nsqrt(nlog n)) 的算法,不过太难写了。
大概就是在重构时充分利用原奇偶序列的有序性,归并两个有序数列可以在线性时间内完成,于是可以线性时间完成重构。然后再重新调整块的大小使询问与修改的复杂度平衡。
这个常数说不定还不如上面那个算法……

@accepted code@

#include<cstdio>
#include<algorithm>
using namespace std;
#define lb lower_bound
const int BLOCK = 320;
const int MAXN = 100000;
int le[MAXN/BLOCK + 5], ri[MAXN/BLOCK + 5], add[MAXN/BLOCK + 5], num[MAXN + 5];
int arr[2][MAXN/BLOCK + 5][BLOCK + 5], siz[2][MAXN/BLOCK + 5];
int n, q, p, bcnt;
int a[MAXN + 5];
void rebuild(int x) {
	siz[0][x] = siz[1][x] = 0;
	for(int i=le[x];i<=ri[x];i++)
		arr[a[i]&1][x][++siz[a[i]&1][x]] = a[i];
	sort(arr[0][x] + 1, arr[0][x] + siz[0][x] + 1);
	sort(arr[1][x] + 1, arr[1][x] + siz[1][x] + 1);
}
void build() {
	bcnt = (n-1)/BLOCK + 1;
	for(int i=0;i<n;i++) {
		if( i % BLOCK == 0 )
			le[i/BLOCK+1] = ri[i/BLOCK+1] = i;
		else ri[i/BLOCK+1]++;
		num[i] = i/BLOCK+1;
	}
	for(int i=1;i<=bcnt;i++)
		rebuild(i);
}
void pushdown(int x) {
	for(int i=le[x];i<=ri[x];i++)
		a[i] = (a[i] + add[x])%(p+1);
	add[x] = 0;
}
int fun(int x) {
	if( x == p ) return 2;
	else return (x&1);
}
int main() {
	freopen("right.in", "r", stdin);
	freopen("right.out", "w", stdout);
	scanf("%d%d%d", &n, &q, &p);
	if( p % 2 == 1 ) p = 1;
	for(int i=0;i<n;i++)
		scanf("%d", &a[i]), a[i] %= (p+1);
	build();
	for(int i=1;i<=q;i++) {
		int tp; scanf("%d", &tp);
		if( tp == 0 ) {
			int l, r, x; scanf("%d%d%d", &l, &r, &x), l--, r--, x %= (p+1);
			if( num[l] == num[r] ) {
				pushdown(num[l]);
				for(int i=l;i<=r;i++)
					a[i] = (a[i] + x)%(p+1);
				rebuild(num[l]);
			}
			else {
				for(int i=num[l]+1;i<=num[r]-1;i++)
					add[i] = (add[i] + x)%(p+1);
				pushdown(num[l]);
				for(int i=l;i<=ri[num[l]];i++)
					a[i] = (a[i] + x)%(p+1);
				rebuild(num[l]);
				pushdown(num[r]);
				for(int i=le[num[r]];i<=r;i++)
					a[i] = (a[i] + x)%(p+1);
				rebuild(num[r]);
			}
		}
		else {
			int l, r, ans = 0; scanf("%d%d", &l, &r), l--, r--;
			if( num[l] == num[r] ) {
				for(int i=l;i<=r;i++)
					ans ^= fun((a[i] + add[num[i]])%(p+1));
			}
			else {
				for(int i=l;i<=ri[num[l]];i++)
					ans ^= fun((a[i] + add[num[i]])%(p+1));
				for(int i=le[num[r]];i<=r;i++)
					ans ^= fun((a[i] + add[num[i]])%(p+1));
				for(int i=num[l]+1;i<=num[r]-1;i++) {
					int t1 = p-add[i];
					int x = upper_bound(arr[t1&1][i]+1, arr[t1&1][i]+siz[t1&1][i]+1, t1) - arr[t1&1][i];
					int y = lower_bound(arr[t1&1][i]+1, arr[t1&1][i]+siz[t1&1][i]+1, t1) - arr[t1&1][i];
					int z = upper_bound(arr[t1&1^1][i]+1, arr[t1&1^1][i]+siz[t1&1^1][i]+1, t1) - arr[t1&1^1][i];
					if( (x-y) & 1 ) ans ^= 2;
					if( p != 1 && ((z-1+siz[t1&1][i]-x+1) & 1) ) ans ^= 1;
				}
			}
			puts(ans ? "1" : "0");
		}
	}
}
/*
以下是打表用程序:
#include<cstdio>
const int MAXN = 100;
int sg[MAXN + 5];
bool tag[MAXN + 5];
int main() {
	int p; scanf("%d",Q &p);
	sg[0] = 0;
	for(int i=1;i<=MAXN;i++) {
		for(int j=0;j<=MAXN;j++)
			tag[j] = false;
		tag[sg[i-1]] = true;
		if( p != 1 ) {
			int x = p;
			while( x <= i ) {
				tag[sg[i-x]] = true;
				x *= p;
			}
		}
		for(int j=0;;j++)
			if( !tag[j] ) {
				sg[i] = j;
				break;
			}
	}
	for(int i=0;i<=MAXN;i++)
		printf("%d : %d
", i, sg[i]);
}
*/

@details@

康复计划 - 9。

当我对博弈论一筹莫展时,旁边的 zxb 大佬小声地提醒我说:“打表”。突然就幡然醒悟了。
这场比赛的 T2 我还没写,因为我还没有理解随机状况下标算时间复杂度怎么来的。以后慢慢填坑(咕咕咕)。

原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/11095171.html