zoukankan      html  css  js  c++  java
  • 10Day抢救计划

    算是强迫自己复习一次做的题吧

    iPlayForSG不会SG函数(大嘘


    P2197 【模板】nim游戏

    题目名字说明了一切qwq

    • 定理:(Nim)博弈先手必胜,当且仅当(A_1 xorA_2xor...xorA_n eq0)

    • 证明:读者自证不难所有石子都被取光显然是一个必败局面(对手已经胜利),显然此时异或和为(0)(啥都没有了)。对于任意一个局面,如果异或和不为(0),设这个和为(x),且其二进制下最高位的(1)在第(k)位,那么至少存在一堆石子(A_i),它的第(k)位是(1),显然(x) (xor) (A_i<A_i)。那么我们就从第(A_i)堆中取走(A_i-A_i) (xor) (x)个石子,就得到了一个异或和为(0)的局面。

    根据题意,我们将所有石子的个数异或起来,只要异或和不为(0)就先手必胜。

    代码略。

    P1247 取火柴游戏

    上一个题的加强版所以为什么上一个是蓝题这个是绿题呢

    首先还是考虑(Nim)博弈,异或和为(0)则先手必败。

    当先手必胜时,本题还要求我们构造一种方案。我们可以按照上述证明过程来构造,但是还有一种更简洁的方法。

    考虑到异或的性质:对于任意一个整数(x),满足(x) (xor) (x=0)。这启发我们:序列(A)异或和为(0)的时候一定存在某个数(A_i),使得序列中其它数异或和为(A_i)。那么我们直接按编号从小到大遍历原序列,找到某个数,使得序列中其它的数的异或和小于它,然后我们考虑将这个数与其它数的异或和作差,那么拿走这些石头就相当于构造出了异或和为(0)的必败局面。其它数的异或和可以用异或前缀和快速求解。

    代码如下,已省略数组声明和宏定义声明

    int main()
    {
    	read(n);
    	for (rg int i = 1; i <= n; ++i) read(a[i]), s[i] = s[i - 1] ^ a[i];
    	if (!s[n])
    	{
    		puts("lose");
    		return 0;
    	}
    	for (rg int i = 1; i <= n; ++i)
    	{
    		rg int tmp = s[i - 1] ^ (s[n] ^ s[i]);
    		if (tmp >= a[i]) continue;
    		print(a[i] - tmp), __space, print(i), __endl;
    		a[i] = tmp;
    		for (rg int j = 1; j <= n; ++j) print(a[j]), __space;
    		return 0;
    	}
    	return 0;
    }
    

    HDU1846 Brave Game

    相信你看的出来这个题是裸的SG函数。考虑递推求解即可

    代码如下,已省略数组声明和宏定义声明

    int main()
    {
    	read(t);
    	while (t--)
    	{
    		read(n), read(m);
    		for (rg int i = 1; i <= n; ++i)
    		{
    			memset(mex, false, sizeof mex);
    			for (rg int j = 1; j <= i && j <= m; ++j) mex[SG[i - j]] = true;
    			for (rg int j = 0; ; ++j)
    			{
    				if (mex[j]) continue;
    				SG[i] = j;
    				break;
    			}
    		}
    		if (SG[n]) puts("first");
    		else puts("second");
    	}
    	return 0;
    }
    

    HDU1847 Good Luck in CET-4 Everybody!

    提出两种解法。最暴力的做法是SG函数打表。考虑到这堆牌至多(1000)张,那么我们预处理出所有一次取小于等于(1000)张牌的方法,直接打表预处理每种牌数的可能性。

    inline void SG_Form(int n) // 打表写法, 通常情况首选 
    {
    	for (rg int i = 1; i <= n; ++i)
    	{
    		// 新的SG, 重置后继集合 
    		memset(S, false, sizeof S);
    		for (rg int j = 1; f[j] <= i && j <= n; ++j)
    		{
    			S[SG[i - f[j]]] = true; // 标记后继SG函数值 
    		}
    		for (rg int j = 0; ; ++j) // Mex运算 
    		{
    			if (!S[j])
    			{
    				SG[i] = j;
    				break;
    			}
    		}
    	}
    }
    int n;
    int main()
    {
    	f[1] = 1;
    	for (rg int i = 2; i <= 15; ++i) f[i] = f[i - 1] * 2;
    	SG_Form(1001);
    	while (~scanf("%d", &n))
    	{
    		if (SG[n]) puts("Kiki");
    		else puts("Cici");
    	}
    	return 0;
    }
    

    当然,还有一种巴什博奕的求解方法,你也可以通过输出SG函数来找到这个规律。事实上,暴力求解SG函数通常是不可取的,打表SG函数一般的作用都是找规律。。

    考虑让对手陷入必败态。每人每次取的牌数都是(2)的幂次方。我们要构造一种局面让对手不能一次取完所有牌。考虑一个最小的、其倍数不可能是(2)的幂次方的数,显然(3)满足条件。事实上,只要牌的总数不是(3)的倍数则先手必胜,因为他总有一种取法让牌数回到(3)的倍数而对手无法一次性取完所有牌。

    证明略,代码略。

    HDU1848 Fibonacci again and again

    HDU1847 Good Luck in CET-4 Everybody!的SG函数求法一样,仅将可出牌的情况预处理为(Fibonacci)即可。

    代码略。

    HDU1849 Rabbit and Grass

    这个题初看没有什么头绪,但是我们可以考虑转换一波题意。

    注意到该游戏具有以下规则

    每一步可以选择任意一个棋子向左移动到任意的位置(可以多个棋子位于同一个方格),当然,任何棋子不能超出棋盘边界;

    如果所有的棋子都位于最左边(即编号为0的位置),则游戏结束,并且规定最后走棋的一方为胜者。

    考虑转换,把每个棋子分别看做一堆石子,那么它的个数相当于其所在位置的编号。将棋子向左移动相当于取石子,棋子位于最左边相当于取完石子。现在此题转换为了裸的(Nim)博弈游戏。异或和判断即可

    代码略

    HDU1850 Being a Good Boy in Spring Festival

    P1247 取火柴游戏基本一样,只是要求方案总数。考虑到两数异或和为(0)当且仅当两数相等,所以每一堆石子最多有一种取法。直接遍历输出即可。

    代码略

    GZOI2015 T1 石子游戏

    一眼SG函数,但是显然1e6的数据范围并不支持我们暴力递推SG函数。还记得刚刚怎么说的吗

    事实上,暴力求解SG函数通常是不可取的,打表SG函数一般的作用都是找规律

    考虑打表

    inline void SG_Form(int r)
    {
    	for (rg int i = 1; i <= r; ++i)
    	{
    		memset(mex, false, sizeof mex);
    		for (rg int j = 1; j <= i; ++j)
    		{
    			if (gcd(i, j) != 1) continue; // 取石子要满足gcd = 1
    			mex[SG[i - j]] = true;
    		}
    		for (rg int j = 1; ; ++j) // 不从0开始是因为可以取走整堆石子
    		{
    			if (mex[j]) continue;
    			SG[i] = j;
    			break;
    		}
    	}
    }
    

    然后可以得到下面这样一张表

    1: 1
    2: 2
    3: 3
    4: 2
    5: 4
    6: 2
    7: 5
    8: 2
    9: 3
    10: 2
    11: 6
    12: 2
    13: 7
    14: 2
    15: 3
    16: 2
    17: 8
    18: 2
    19: 9
    20: 2
    ...
    

    看起来除了偶数全是(2),质数递增其他都没规律。然而考虑到质数递增相当于它自己在质数中的排名,带入偶数就是其最小质因数(2)的排名。检验一下奇数,发现也满足条件。

    整合一下就是:

    • 对于(1)而言,SG值为(1)
    • 对于其他数而言,SG值为其最小质因数在质数中的排名

    那么直接暴力线性筛就好,甚至不需要预处理每个数的SG。

    代码如下

    const int maxn = 23333;
    int SG[maxn];
    bool mex[maxn];
    inline int gcd(int x, int y)
    {
    	if (!y) return x;
    	return gcd(y, x % y);
    }
    inline void SG_Form(int r)
    {
    	for (rg int i = 1; i <= r; ++i)
    	{
    		memset(mex, false, sizeof mex);
    		for (rg int j = 1; j <= i; ++j)
    		{
    			if (gcd(i, j) != 1) continue;
    			mex[SG[i - j]] = true;
    		}
    		for (rg int j = 1; ; ++j) // 不从0开始是因为可以取走整堆石子
    		{
    			if (mex[j]) continue;
    			SG[i] = j;
    			break;
    		}
    	}
    }
    int tot;
    int pri[1000005];
    bool flag[1000005];
    inline void make_pri_list(int r)
    {
    	flag[1] = true;
    	for (rg int i = 2; i <= r; ++i)
    	{
    		if (!flag[i]) pri[++tot] = i;
    		for (rg int j = 1; j <= tot && i * pri[j] <= r; ++j)
    		{
    			flag[i * pri[j]] = true;
    			if (!(i % pri[j])) break;
    		}
    	}
    }
    int t, n, x;
    int main()
    {
    	make_pri_list(1000000);
    //	freopen("form.out", "w", stdout);
    //	SG_Form(2333);
    //	for (rg int i = 1; i <= 2333; ++i) printf("%d: %d
    ", i, SG[i]);
    //	return 0;
    	read(t);
    	while (t--)
    	{
    		read(n);
    		rg int ans = 0;
    		for (rg int i = 1; i <= n; ++i)
    		{
    			read(x);
    			if (!flag[x]) x = lower_bound(pri + 1, pri + 1 + tot, x) - pri + 1;
    			else if (x != 1)
    			{
    				for (rg int j = 1; j <= tot; ++j)
    				{
    					if (!(x % pri[j]))
    					{
    						x = pri[j];
    						break;
    					}
    				}
    				x = lower_bound(pri + 1, pri + 1 + tot, x) - pri + 1;
    			}
    			ans ^= x;
    		}
    		if (ans) puts("Alice");
    		else puts("Bob");
    	}
    	return 0;
    }
    

    CF1194D 1-2-K Game

    遇事不决先SG打表

    (1 1): 1, (1 2): 1, (1 3): 1, (1 4): 1, (1 5): 1, (1 6): 1, (1 7): 1, (1 8): 1, (1 9): 1, (1 10): 1, 
    (2 1): 2, (2 2): 2, (2 3): 2, (2 4): 2, (2 5): 2, (2 6): 2, (2 7): 2, (2 8): 2, (2 9): 2, (2 10): 2, 
    (3 1): 0, (3 2): 0, (3 3): 3, (3 4): 0, (3 5): 0, (3 6): 0, (3 7): 0, (3 8): 0, (3 9): 0, (3 10): 0, 
    (4 1): 1, (4 2): 1, (4 3): 0, (4 4): 1, (4 5): 1, (4 6): 1, (4 7): 1, (4 8): 1, (4 9): 1, (4 10): 1, 
    (5 1): 2, (5 2): 2, (5 3): 1, (5 4): 2, (5 5): 2, (5 6): 2, (5 7): 2, (5 8): 2, (5 9): 2, (5 10): 2, 
    (6 1): 0, (6 2): 0, (6 3): 2, (6 4): 0, (6 5): 0, (6 6): 3, (6 7): 0, (6 8): 0, (6 9): 0, (6 10): 0, 
    (7 1): 1, (7 2): 1, (7 3): 3, (7 4): 1, (7 5): 1, (7 6): 0, (7 7): 1, (7 8): 1, (7 9): 1, (7 10): 1, 
    (8 1): 2, (8 2): 2, (8 3): 0, (8 4): 2, (8 5): 2, (8 6): 1, (8 7): 2, (8 8): 2, (8 9): 2, (8 10): 2, 
    (9 1): 0, (9 2): 0, (9 3): 1, (9 4): 0, (9 5): 0, (9 6): 2, (9 7): 0, (9 8): 0, (9 9): 3, (9 10): 0, 
    (10 1): 1, (10 2): 1, (10 3): 2, (10 4): 1, (10 5): 1, (10 6): 0, (10 7): 1, (10 8): 1, (10 9): 0, (10 10): 1, 
    

    这是一部分10x10的表,如果你打的够大你会很容易发现规律:

    • (k)不是(3)的倍数,

      • n是(3)的倍数则(Bob)

      • 否则(Alice)

    • (k)(3)的倍数

      • (n)在模(k+1)后是(3)的倍数且(n)不等于(k)(Bob)

      • 否则(Alice)

    代码就不上了

    (后面就没那么裸了)

    LOJ10243 移棋子游戏

    初看题目没什么思路。考虑一下SG函数的本质:

    • 有向图游戏的某个局面必胜(/)败,当且仅当该局面对应节点的SG函数值大于(/)等于(0)

    显然,出度为(0)的点SG函数一定为(0)。考虑从这些点倒推,由于每次只能移动一步,那该点的上一个局面一定是它的入边上的另一节点,由此可以进行SG函数的递归求解。由于原图是(DAG),采用拓扑排序解决。

    inline void topsort()
    {
    	while (!q.empty())
    	{
    		memset(mex, false, sizeof mex);
    		rg int x = q.front();
    		q.pop();
    		for (rg int i = head[x]; i; i = e[i].next)
    		{
    			rg int v = e[i].v;
    			mex[SG[v]] = true;
    		}
    		for (rg int i = 0; ; ++i)
    		{
    			if (mex[i]) continue;
    			SG[x] = i;
    			break;
    		}
    		for (rg int i = rhead[x]; i; i = re[i].next)
    		{
    			rg int v = re[i].v;
    			if (!(--oud[v])) q.push(v);
    		}
    	}
    }
    int x, y;
    int main()
    {
    	read(n), read(m), read(k);
    	for (rg int i = 1; i <= m; ++i) read(x), read(y), add(x, y), ++oud[x];
    	for (rg int i = 1; i <= n; ++i)
    	{
    		if (oud[i]) continue;
    		q.push(i);
    	}
    	topsort();
    	for (rg int i = 1; i <= k; ++i) read(x), ans ^= SG[x];
    	if (ans) puts("win");
    	else puts("lose");
    	return 0;
    }
    

    LOJ10244 取石子游戏

    前一半是个裸题,后面对于神仙来说还是裸题输出方案需要进行一定的转换。考虑到先手必胜则下一步先手必败,先手必败态的SG函数异或和为(0),所以我们要构造一种方案使得SG函数异或和为(0)。想一想SG函数的更新是如何得到的

    for (rg int j = 1; 第j个取石子的方案取几个石子 <= 当前石子数 && j <= 取石子的方案总数; ++j)
    	mex[SG[当前石子数 - 第j个取石子的方案取几个石子]] = true;
    

    我们也可以像这样进行更新。当前异或和 异或 当前石子的SG值,得到其它石子的SG值异或和,枚举当前石子应取几个石子,再计算取石子后的异或和,为(0)则达到了先手必败态。

    inline void SG_Form(int r)
    {
    	memset(SG, 0, sizeof SG);
    	for (rg int i = 1; i <= r; ++i)
    	{
    		memset(mex, false, sizeof mex);
    		for (rg int j = 1; b[j] <= i && j <= m; ++j)
    		{
    			mex[SG[i - b[j]]] = true;
    		}
    		for (rg int j = 0; ; ++j)
    		{
    			if (mex[j]) continue;
    			SG[i] = j;
    			break;
    		}
    	}
    }
    int main()
    {
    	read(n);
    	for (rg int i = 1; i <= n; ++i) read(a[i]);
    	read(m);
    	for (rg int i = 1; i <= m; ++i) read(b[i]);
    	SG_Form(2333);
    	for (rg int i = 1; i <= n; ++i) ans ^= SG[a[i]];
    	if (!ans) puts("NO");
    	else
    	{
    		puts("YES");
    		for (rg int i = 1; i <= n; ++i)
    		{
    			for (rg int j = 1; b[j] <= a[i] && j <= m; ++j)
    			{
    				if (ans ^ SG[a[i]] ^ SG[a[i] - b[j]]) continue;
    				print(i), __space, print(b[j]);
    				return 0;
    			}
    		}
    	}
    	return 0;
    }
    

    LOJ10245 巧克力棒

    把巧克力棒看做一堆能吃的石头,题意转换为每次可以选择制造若干堆石头,或者取石头。

    最开始读题时感觉这题一脸不可做,但是思考后发现:如果总局面(不考虑制造石头,只考虑有(n)堆石头)是先手必败,则先手必胜。

    感觉有点绕对不对,这样考虑:如果先手必败,那么我先手直接把所有石头制造出来,相当于把先手必败态扔给对面了,对面先手必败那我就必胜。

    这样一来,题目就变成寻找是否存在一个子序列,使得这些石头构成先手必败局面

    为什么?因为先手可以把这些石头制造出来,把必败态扔给对面。如果对面和我玩这些石头,那他必败,他制造石头就相当于破坏了先手必败态,然而这之后是我先手,他还是必败。

    inline void calc()
    {
    	rg int cnt = 0, tans = 0;
    	for (rg int i = 1; i <= n; ++i)
    	{
    		if (vis[i]) tans ^= a[i], ++cnt;
    	}
    	if (!cnt) return;
    	if (!tans)
    	{
    		flag = true;
    		puts("NO");
    		return;
    	}
    }
    inline void dfs(int id)
    {
    	if (flag) return;
    	if (id == n + 1)
    	{
    		calc();
    		return;
    	}
    	vis[id] = true;
    	dfs(id + 1);
    	vis[id] = false;
    	dfs(id + 1);
    }
    int main()
    {
    	while (t--)
    	{
    		read(n);
    		ans = 0, flag = false;
    		for (rg int i = 1; i <= n; ++i) read(a[i]), ans ^= a[i];
    		if (!ans) puts("NO");
    		else
    		{
    			dfs(1);
    			if (!flag) puts("YES");
    		}
    	}
    	return 0;
    }
    

    LOJ10246 取石子

    !神仙题警告←神仙无视

    这道题我开始还想暴力SG给艹过去,然后发现我想多了。。。

    首先考虑规则:一次只能拿(1)个或者合并(2)堆。

    如果只考虑第一个情况,那奇数个石子就先手必胜,偶数个石子就先手必败。这个题他难就难在可以进行合并操作。。

    考虑一下:当你面临一个先手必败态,你是不是可以考虑选择合并两堆石子来把这个必败态转移给对面?

    你可能会开心地想:那我再判一下石子堆数的奇偶性不就行了吗?

    然后。。

    继续考虑。发现如果某堆石子个数为(1),那它被拿了后就没机会参与合并操作了。但是在没拿它的时候它还是可以被合并。

    考虑把所有为(1)的堆单独提出来搞一搞。我们把合并操作当做多一个石子,加在其他的堆里面(本质都是消耗一次操作),注意在求和后要减一,因为最后一堆石子不能合并了。

    那就好办了。如果现在石子数是奇数或者(1)的个数为奇数,那就是必胜态,因为如果石子数是偶数那可以考虑用(1)来转移必胜态。

    当然还要特判其他石子加起来都没有(3)个的情况,这个时候就要看(1)的个数,(1)的个数不是(3)的倍数就必胜,否则必败(因为它可以用来 合并也可以用来取掉)。

    int main()
    {
    	read(t);
    	while (t--)
    	{
    		read(n);
    		ans = tot = 0;
    		for (rg int i = 1; i <= n; ++i)
    		{
    			read(x);
    			if (x == 1) ++tot;
    			else ans += x + 1;
    		}
    		--ans;
    		if (ans >= 3)
    		{
    			if ((ans & 1) || (tot & 1)) puts("YES");
    			else puts("NO");
    		}
    		else
    		{
    			if (tot % 3) puts("YES");
    			else puts("NO");
    		}
    //		if (n & 1) ++ans;
    //		if (!(ans & 1)) puts("NO");
    //		else puts("YES");
    	}
    	return 0;
    }
    
  • 相关阅读:
    Individual Method Selection Survey rubric
    Xcode 出现Thread 1: signal SIGABRT
    C/C++生成随机数
    《深入浅出深度学习:原理剖析与python实践》第八章前馈神经网络(笔记)
    操作系统--精髓与设计原理(第八版)第三章复习题答案
    操作系统--精髓与设计原理(第八版)第二章复习题答案
    Python知识点整理
    C++ <queue>用法
    C语言结构体用法
    Mac安装Qt出现错误Could not resolve SDK Path for 'macosx'
  • 原文地址:https://www.cnblogs.com/Here-is-SG/p/11799819.html
Copyright © 2011-2022 走看看