2021年7月26日 构造、博弈论训练题题解

【新人,拿这篇博客练练手,大佬们轻喷QAQ】

训练题都来源于cf hdu等各大OJ,这里就不标记出处了。(其实就是我懒)


  • A - Matches Game

Here is a simple game. In this game, there are several piles of matches and two players. The two player play in turn. In each turn, one can choose a pile and take away arbitrary number of matches from the pile (Of course the number of matches, which is taken away, cannot be zero and cannot be larger than the number of matches in the chosen pile). If after a player’s turn, there is no match left, the player is the winner. Suppose that the two players are all very clear. Your job is to tell whether the player who plays first can win the game or not.

Input

The input consists of several lines, and in each line there is a test case. At the beginning of a line, there is an integer M (1 <= M <=20), which is the number of piles. Then comes M positive integers, which are not larger than 10000000. These M integers represent the number of matches in each pile.

Output

For each test case, output "Yes" in a single line, if the player who play first will win, otherwise output "No".
这道题就是一个简单的Nim游戏,输入数据,直接异或求Nim和判断就可以了。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
int main(){
	int n;
	while(cin>>n){
		int a;
		int ans=0;
		for(int i=0;i<n;++i){
			cin>>a;
			ans^=a;
		}
		if(ans==0) cout<<"No"<<endl;
		else cout<<"Yes"<<endl;
	}
	return 0;
}
  • B - Georgia and Bob
Georgia and Bob decide to play a self-invented game. They draw a row of grids on paper, number the grids from left to right by 1, 2, 3, ..., and place N chessmen on different grids, as shown in the following figure for example:

Georgia and Bob move the chessmen in turn. Every time a player will choose a chessman, and move it to the left without going over any other chessmen or across the left edge. The player can freely choose number of steps the chessman moves, with the constraint that the chessman must be moved at least ONE step and one grid can at most contains ONE single chessman. The player who cannot make a move loses the game.

Georgia always plays first since "Lady first". Suppose that Georgia and Bob both do their best in the game, i.e., if one of them knows a way to win the game, he or she will be able to carry it out.

Given the initial positions of the n chessmen, can you predict who will finally win the game?

Input

The first line of the input contains a single integer T (1 <= T <= 20), the number of test cases. Then T cases follow. Each test case contains two lines. The first line consists of one integer N (1 <= N <= 1000), indicating the number of chessmen. The second line contains N different integers P1, P2 ... Pn (1 <= Pi <= 10000), which are the initial positions of the n chessmen.

Output

For each test case, prints a single line, "Georgia will win", if Georgia will win the game; "Bob will win", if Bob will win the game; otherwise 'Not sure'.
这道题我在三个日本选手写的《挑战程序设计竞赛》上看到过,所以当时写起来没有太大的压力。读入棋子所在的编号,存入数组a[n]中,如果棋子数n是偶数(这里用2k表示),就把编号升序排序后,把第 2i-1 个和第 2i 个组合在一起,就是<a[1] , a[2]> <a[3] , a[4]> ... <a[2k-1] , a[2k]>一组。如果是奇数,即n=2k-1,就额外补充一个编号0,然后同样操作即可。
这么处理的目的是,我们把每一对棋子中间的空余的格子数目当成Nim游戏里面的每堆石子数,如示例图中<a[1] , a[2]>即<1 , 3>堆中石子数1,<a[3] , a[4]>堆即<6 , 7>中石子数0。由于每个棋子只能左移,所以若是每个区间的右端点左移,就相当于Nim游戏里面的每一堆里面取走任意数量的石子,若是左端点左移,相当于区间间隔拉长,则后手可以把右端点左移同样的格数,恢复到之前的状态。所以,用Nim游戏的模型来解决此题就行。
点击查看代码
#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
    int t;cin >> t;
    while(t--){
        int n;
        cin >> n;
        int a[1010];
        for(int i=1;i<=n;++i){
            cin >> a[i];
        }
        if(n%2==1){
            a[++n]=0;
        }
        sort(a+1,a+n+1);
        int sum=0;
        for(int i=1;i<=n;i+=2){
            sum^=(a[i+1]-a[i]-1);
        }
        if(sum==0){
            cout << "Bob will win" << endl;
        }else{
            cout << "Georgia will win" << endl;
        }
    }
    return 0;
}
  • C - Deleting Divisors

Alice and Bob are playing a game.

They start with a positive integer nn and take alternating turns doing operations on it. Each turn a player can subtract from nn one of its divisors that isn't 11 or nn. The player who cannot make a move on his/her turn loses. Alice always moves first.

Note that they subtract a divisor of the current number in each turn.

You are asked to find out who will win the game if both players play optimally.

Input

The first line contains a single integer t (1t104) — the number of test cases. Then t test cases follow.

Each test case contains a single integer n (1n109) — the initial number.

Output

For each test case output "Alice" if Alice will win the game or "Bob" if Bob will win, if both players play optimally.

解出此题全感谢GJK的提示qaq
如果读入的n是质数,那么先手必输。如果读入的是非质数的奇数,那么它至少有两个不为1或n的奇数因子。先手减去一个因子,变为“偶数*奇数”的形式,后手减去奇数因子,又回到了“奇数*奇数”的状态,反复多次以后,交给先手的便是一个质数。因此,若n是奇数,则先手必输。
如果读入的n是偶数,先判断这个偶数有没有奇数因子,即看它能否写成k*2p的形式,其中k是奇数。如果可以,就转换到上面讨论的“偶数*奇数”形式,根据分析,这种情况是必胜态,先手必赢。
如果这个偶数是2p形式的,若先手减去一个非n/2的因子,那么交给后手的就是“非1奇数*偶数”的形式,自己必败,所以唯一的策略就是减去n/2,交给对方一个1*(n/2)形式的数。多轮下来,谁先给对方数字2,谁就是赢家。所以,p为奇数时先手必败,否则先手必胜。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
int main() {
	int t;
	cin >> t;
	while (t--) {
		long long n;
		cin >> n;
		if (n % 2 == 1) {
			cout << "Bob" << endl;
		} else {
			long long p = 0;
			while (n % 2 == 0) {
				n /= 2;
				++p;
			}
			if (n != 1) {
				cout << "Alice" << endl;
			} else {
				if (p % 2 == 1) {
					cout << "Bob" << endl;
				} else {
					cout << "Alice" << endl;
				}
			}
		}
	}
	return 0;
}
 
  • D - 取石子游戏
1堆石子有n个,两人轮流取.先取者第1次可以取任意多个,但不能全部取完.以后每次取的石子数不能超过上次取子数的2倍。取完者胜.先取者负输出"Second win".先取者胜输出"First win".

Input

输入有多组.每组第1行是2<=n<2^31. n=0退出.
Output

先取者负输出"Second win". 先取者胜输出"First win".

全场唯一一道中文题。本题手推观察可得,若石子数目为斐波那契数列的某一项,则先手必败。当然,我写这个题的时候是观察出来的,讲题的时候学姐给了一个证明,链接在此
点击查看代码
#include <bits/stdc++.h>
using namespace std;
int main(){
	long long t[10010];
	t[0]=1;t[1]=1;
	int i;
	for(i=2;;++i){
		if(t[i-1]+t[i-2]>2147483647){
			break;
		}
		else{
			t[i]=t[i-1]+t[i-2];
		}
	}
	--i;
	int a;
	cin>>a;
	while(a){
		bool flag=true;
		for(int z=1;z<=i;++z){
			if(a==t[z]){
				flag=false;
				break;
			}
			if(a>t[z]&&a<t[z+1]){
				break;
			}
		}
		if(flag){
			cout<<"First win"<<endl;
		}else{
			cout<<"Second win"<<endl;
		}	
		cin>>a;
	}
	return 0;
}
  • E - A Multiplication Game
Stan and Ollie play the game of multiplication by multiplying an integer p by one of the numbers 2 to 9. Stan always starts with p = 1, does his multiplication, then Ollie multiplies the number, then Stan and so on. Before a game starts, they draw an integer 1 < n < 4294967295 and the winner is who first reaches p >= n.

Input

Each line of input contains one integer number n.

Output

For each line of input output one line either
Stan wins.
or
Ollie wins.
assuming that both of them play perfectly.

当2<=n<=9时先手必胜,9<n<=18时,先手最小只能乘以2,后手只要乘以9就可以保证胜利,后手必胜。
18<n<=162 ( 9*18 ) 时,先手乘以9,后手最小乘以2到18,则从19开始先手有必胜策略,先手乘以2,后手最大乘以9到18,先手再乘以9到162。因此这个区间是先手必胜区间。
由此,只要对于每个给定的数n,让它每次除以18再与9比较大小,小于等于9则先手胜利,否则后手胜。
本题有个史诗巨坑:输出的内容包括最后的句点!!!我在此题WA了无数发,才注意到这个事情quq……
点击查看代码
#include <bits/stdc++.h>
using namespace std;
int main(){
	double n;
	while(cin>>n){
		while(n>18){
			n/=18;
		}
		if(n>9){
			cout<<"Ollie wins."<<endl;
		}
		else{
			cout<<"Stan wins."<<endl;
		}
	}
	return 0;
}
  • F - Hack it!

 

Little X has met the following problem recently.

Let's define f(x) as the sum of digits in decimal representation of number x (for example, f(1234) = 1 + 2 + 3 + 4). You are to calculate 

Of course Little X has solved this problem quickly, has locked it, and then has tried to hack others. He has seen the following C++ code:

    ans = solve(l, r) % a;
    if (ans <= 0)
      ans += a;

This code will fail only on the test with . You are given number a, help Little X to find a proper test for hack.

Input

The first line contains a single integer a (1 ≤ a ≤ 1018).

Output

Print two integers: l, r (1 ≤ l ≤ r < 10200) — the required test data. Leading zeros aren't allowed. It's guaranteed that the solution exists.

此题是Codeforces Div1的D题,虽然集训的时候讲过,但是听的云里雾里……后来在面向博客园和CSDN编程的情况下才慢慢理解这个题的意思。
首先,我们知道,对每一个正整数k都有 f(10k+x) = f(x) + 1,因为10k在这个函数中的贡献只有最高位的1。题给数据在0~1e18,则有f(1e18+x)=f(x)+1。
假设从f(1)一直加到f(1e18)的值为m。则从f(2)加到f(1e18+1)的值为m+2-1=m+1,f(3)加到f(1e18+2)的值为m+2,……故区间右移n个单位,和便增加n。我们只需找到一个偏移量d,让m+d可以被a整除就可以了,此时l=1+d,r=1e18+1+d。这个偏移量也比较好想,就是d=a-(m%a)。最后的公式就是l=a-m%a+1,r=1e18+l-1。
通过归纳总结,可以算出1到1e18-1(即999999999999999999)的f(x)和为81*1e18。所以m=1+81*1e18。由于这个数在C++中比long long能存的最大数还要大,取模运算我又烂的要死,在无数发WA之后我直接选择上了JAVA的大整数类,求各位大佬手下留情qaq~
点击查看代码
import java.math.*;
import java.util.*;
public class Hack_it {
	public static void main(String[] args) {
		Scanner in=new Scanner(System.in);
		String s=in.nextLine();
		BigInteger a=new BigInteger(s);
		BigInteger m=new BigInteger("81000000000000000000");
		BigInteger t=m.mod(a);
		BigInteger l=a.subtract(t);
		BigInteger r=l.subtract(new BigInteger("1")).add(new BigInteger("1000000000000000000"));
		System.out.println(l+" "+r);
		in.close();
	}
}
 
 
 
 
 
 
 
 
 
 
posted @ 2021-07-28 00:44  AlexHoring  阅读(139)  评论(0编辑  收藏  举报