zoukankan      html  css  js  c++  java
  • @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 我还没写,因为我还没有理解随机状况下标算时间复杂度怎么来的。以后慢慢填坑(咕咕咕)。

  • 相关阅读:
    树的计数 + prufer序列与Cayley公式 学习笔记
    链表
    密码学摘要
    查找与排序
    匿名内部类 调用方法内局部变量
    <c:url>标签相关知识点
    Oracle 导入导出数据库
    oracle cursor
    Oracle 删除用户和表空间------创建表空间和用户
    iOS延迟执行方法
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/11095171.html
Copyright © 2011-2022 走看看