Test 2018-09-14

取石子游戏

$ (nim.pas/c/cpp) $
 

【题目描述】

小林和亮亮正在玩一个取石子的游戏。
石子一共有 $ n $ 堆,其中第 $ i $ 堆恰好有 $ i $ 粒石子。小林先取,亮亮后取,并且两人依次轮流取石。
每一次取石子的人可以选择任意一堆还未被取完的石子,并取走这一堆中任意多粒石子
(注意,不能一粒石子也不取,也不能同时在多堆石 子中取石)。最终,无石可取的人为败。
 
小林和亮亮都十分聪明,他们的每次取石都会采取最优策略。
在经过多次游戏后,小林发现了先手必胜的条件,但他不满足于此,
他想知道,在知道石子的堆数 $ n $ 后,他第一次取石有多少种方式可以获胜。
 

【输入格式】

第一行一个整数 $ T $ ,表示数据组数。
接下来 $ T $ 行每行一个整数 $ n $ ,表示石子的堆数。
 

【输出格式】

每组数据输出一个整数,表示在两个人都采用最佳策略的情况下,
小林第一次取石能够获胜的方式数,如小林必败,则输出 $ 0 $ 。
 

【样例输入】

 2
 2
 3

【样例输出】

 1
 0 

 

【样例解释】

$ n=2 $ 时小林只有一种取胜方式,即取在第二堆石子中取一粒。
$ n=3 $ 时小林必败,因此输出 $ 0 $ 。
 

【数据规模】

对于 $ 20 $ %的数据,$ n le 10 $ ;
对于 $ 50 $ %的数据,$ n le 1000 $ ;
对于 $ 90 $ %的数据,$ n le 10^15 $ ;
对于 $ 100 $ %的数据,$ 1 le n le 10^1000,1 le T le 10 $ 。
 

【题解】

算法一:动态规划或记忆化搜索
期望得分:$ 20 $
 
算法二:此题实际上为 $ Nim $ 游戏,根据 $ Nim $ 游戏的结论,先手必胜当且仅当每一堆石子的石子数的异或和不为 $ 0 $ 。
枚举第一次在哪一堆取几粒石子,看剩下的数异或和是否为 $ 0 $ 。由于异或的逆运算仍然是异或,
因此可以先求出所有数的异或和再每次调整,减少计算量。
时间复杂度:$ O(n^2) $
期望得分:$ 50 $
 
算法三:注意到 $ x $ 为偶数时 $ x quad xor quad (x+1)= 1 $
(可以在二进制下看这个结果,其中^为异或运算符), 可以发现 $ 1~n $ 的异或和 $ S $ 有以下规律:

$ 1. S=1 quad quad n $ % $ 4 =1 $
$ 2. S=n+1 quad n $ % $ 4=2 ( ) 3. S=0 quad quad n $ % $ 4 = 3 ( ) 4. S=n quad quad n $ % $ 4 = 0 $

$ n $ % $ 4=3 $ 时,先手必败,答案为 $ 0 $ 。
$ n $ % $ 4=1 $ 时,在任意编号为奇数的石子堆中取 $ 1 $ 可使异或和变为 $ 0 $ ,且只有这些方案,于是答案为 $ (n+1)/2 $ 。
$ n $ % $ 4=0 $ 或 $ 2 $ 时,$ S $ 与 $ n $ 位数相同。设它们的 最高位为 $ t,S $ 的第 $ t $ 位为 $ 1 $ ,
为使 $ S $ 变为 $ 0 $ ,必须修改一个第 $ t $ 位为 $ 1 $ 的数。
显然修改任一第 $ t $ 位为 $ 1 $ 的数都可以使 $ S $ 变为 $ 0 $ 。答案即为 $ n $ 去掉最高位再加 $ 1 $ 。
时间复杂度:$ O(1) $
期望得分:$ 90 $
 
算法四:
在算法三基础上加上高精度即可得到 $ 100 $ 分。
 

【代码】

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

struct nim
{
	int len, value[1010];
	nim() { len = 1; memset(value, 0, sizeof(value)); }
	
	nim operator =(const char* s)
	{
		len = strlen(s);
		memset(value, 0, sizeof(value));
		for (int i = 0; i < len; i++)
		{
			value[i] = s[len - i - 1] - 48;
		}
		return *this;
	}
	
	nim operator =(const int& x)
	{
		char s[1010];
		sprintf(s, "%d", x);
		*this = s;
		return *this;
	}
	
	nim operator +(const nim& x) const
	{
		nim y;
		y.len = max(len, x.len);
		for (int i = 0; i < len; i++)
		    y.value[i] = x.value[i] + value[i];
		int k = 0;
		for (int i = 0; i < y.len; i++)
        {
        	k += y.value[i];
        	y.value[i] = k % 10;
        	k /= 10;
        }
        if (k > 0)
        {
        	y.value[y.len] = k;
        	y.len++;
        }
        return y;
	}
	
	nim operator += (const nim& x)
	{
		*this = *this + x;
		return *this;
	}
	
	nim operator /= (const int& x) 
	{
		int p = 0;
		for (int i = len - 1; i >= 0; i--)
		{
			p = p * 10 + value[i];
			value[i] = p / x;
			p %= x;
		}
		if (!value[len-1]) len--;
		return *this;
	}
	
	nim operator *= (const int& x)
	{
	    int p = 0;
	    for (int i = 0; i < len; i++)
	    {
	    	p += value[i] * x;
	    	value[i] = p % 10;
	    	p /= 10;
	    }
	    if (p > 0) { value[len] = p; len++; }
	    return *this;
	}
	
	bool operator <=(const nim& x) const
	{
		if (len != x.len) return len < x.len;
		for (int i = len - 1; i >= 0; i--)
		{
		    if (value[i] < x.value[i]) return true;
		    if (value[i] > x.value[i]) return false;
	    }
		return true;
	}
	
	nim operator -=(const nim& x) 
	{
		for (int i = 0; i < len; i++)
		    value[i] -= x.value[i];
		for (int i = 0; i < len; i++)
		    if (value[i] < 0) { value[i] += 10; value[i+1]--; }
		while (len > 1 && value[len-1] == 0) len--;
		return *this;
	}
}a, b;

void writeint(nim x)
{
	for (int i = x.len - 1; i >= 0; i--)
	    printf("%d", x.value[i]);
	printf("
");
}

int main()
{
	freopen("nim.in", "r", stdin);
	freopen("nim.out", "w", stdout);
	int T;
	scanf("%d", &T);
	while (T--)
	{
		char s[1010];
		scanf("%s", s);
		int n = strlen(s);
		if ((s[n-1] - 48) % 2)
		{
			a = s, b = 1, a += b;
			int k = s[n-1] - 48 + 1;
			if (n > 1) k += (s[n-2] - 48) * 10;
			if (! (k % 4) ) { printf("0
"); continue; }
			a /= 2;
			writeint(a);
			continue;
		}
		else
		{
			b = 1;
			a = s;
			while (b <= a) b *= 2;
			b /= 2;
			a -= b;
			b = 1;
			a += b;
			writeint(a);
		}
	}
	return 0;
}

魔数

$ (number.pas/c/cpp) $
 

【题目描述】

小林和亮亮是好朋友。小林有一个幸运数字 $ a $ ,亮亮有一个幸运数字 $ b $ 。
一 个数字称之为“幸运数”当且仅当它只由 $ a $ 和 $ b $ 组成。
小林称一个数为“魔数”, 当且仅当这个数各数位之和为“幸运数”,且这个数字本身也是“幸运数”。
 
举个例子:小林的幸运数字为 $ 2 $ ,亮亮的幸运数字为 $ 6 $ ,那么 $ 23 $ 不是“幸运数”,而 26、222 是“幸运数”。
进一步,$ 222 $ 是“魔数”(因为 $ 2+2+2=6 $ ),而 $ 26 $ 不是“魔数”(因为 $ 2+6=8 $ )。
 
亮亮想要知道,有多少个 $ n $ 位的“魔数”(一个 $ n $ 位数不包含前导 $ 0 $ ),
由于这个数字会很大,所以亮亮只关心这个数模 $ 1000000007(10^9+7 $ ,是一个质数 $ )$ 的结果。
 

【输入格式】

只有一行,包含三个整数:$ a、b、n $ 。意义见题目描述。
 

【输出格式】

只有一个整数,表示 $ n $ 位的“魔数”的个数模 $ 1000000007 $ 的结果。
 

【样例输入】

 2 6 3

【样例输出】

 1

 

【样例解释】

两个幸运数字分别为 $ 2 $ 和 $ 6 $ ,则位数为 $ 3 $ 的“魔数”只有 $ 222 $ 一个。
 

【数据规模】

对于 $ 30 $ %的数据:$ 1≤n≤5 $ ;
对于 $ 60 $ %的数据:$ 1≤n≤100 $ ;
对于 $ 100 $ %的数据:$ 1≤a<b≤9,1≤n≤10^6 $ 。
 

【题解】

算法一:枚举每一个 $ n $ 位数,判断它是否是“魔数”。
时间复杂度:$ O(10^n) $
期望得分:$ 30 $
 
算法二:由于“魔数”首先是“幸运数”,所以它只能由 $ a $ 和 $ b $ 组成,
那么我们枚举 $ a $ 的个数 $ i(0 le i le n)$ , 并判断由 $ i $ 个 $ a $ 和 $ (n-i) $ 个 $ b $ 组成的数字是不是“魔数”,
也就是判断 $ a imes i+(n-i) imes b $ 是否为“幸运数”,如果是,那么我们就找到了一类魔数,由排列组合知识可知
这一类魔数共有 $ C^i_n = frac{n!}{i!(n-i)!} $ 个。所以我们只需要求一系列的组合数之和即可。由
于模数 $ 10^9+7 $ 是一个质数,所以我们在作除法时可以用求逆元的方法进行。
时间复杂度:$ O(n^2) $
期望得分:$ 60 $
 
算法三:算法二的问题在于,每一次求组合数都需要 $ O(n) $ 的时间,然而我们发现,
我们要求的组合数都是 $ C^k_n $ 的形式($ n $ 是固定的),而在求出 $ C^k_n $ 后,
求 $ C^{k+1}_n $ 只需利用公式 $ C^{k+1}_n = C^k_n imes frac{n-k}{k+1} $ ,只有 $ O(1) $ 的复杂度,这就解决了算法二超时的问题。
另外,我们可以采用求逆元的方法来解决除法的问题,也可以把每一个因子和除数都分解质因数,
这样数的乘除运算就转化成了各个质数指数的加减运算。
用标准分解式来保存大数,就避免了用扩展欧几里得算法求逆元。
时间复杂度:$ O(n) $
期望得分:$ 100 $
 

【代码】

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define int long long
#define Mod 1000000007
int a,b,Mx,n,ans,f[1000005];
bool pd(int x){
	while(x){
		if(x%10!=a&&x%10!=b) return 0;
		x/=10;
	}
	return 1;
}
int qpow(int x){
	int res=x,k=Mod-3;
	while(k){
		if(k&1) res=res*x%Mod;
		x=x*x%Mod;
		k>>=1;
	}
	return res;
}
signed main(){
	freopen("number.in","r",stdin);
	freopen("number.out","w",stdout);
	scanf("%lld %lld %lld",&a,&b,&n);
	f[0]=1;
	for(int i=1;i<=n;++i) f[i]=(f[i-1]*i)%Mod;
	int res=n*b; ans+=pd(res);
	for(int i=1;i<=n;++i){
		res=res-b+a;
		if(pd(res)) ans=(ans+f[n]*qpow(f[i])%Mod*qpow(f[n-i])%Mod)%Mod;
	}
	printf("%lld",ans);
	return 0;
}

军训站队

$ (queue.pas/c/cpp) $
 

【题目描述】

小林和亮亮刚入高中,首先就迎来了军训。这一天,他们班的同学正在教官 的指导下进行队列练习。
 
班上总共有 $ n $ 位同学,他们站成一排,然而,本该站成一条直线的他们却排 成了“一字长蛇阵”。
用数学的语言来描述,如果把训练场看成一个平面直角坐标系,第 $ i $ 名同学所在位置的横坐标是 $ i $ ,
而所有同学的纵坐标本该是 $ 0 $(或者任 意一个相等的常量),这样就排成了一条直线。
当然,由于同学们排的歪歪扭扭, 所以第 $ i $ 位同学的横坐标依然是 $ i $ ,而纵坐标却成了 $ Y_i $
(为了简化问题,我们假设所有的坐标都是整数)。
 
对此,教官当然十分生气,因此他决定下命令调整队伍,使得所有人能够站成一条直线(也即让所有的 $ Y_i $ 相同)。
教官的命令总共有三种:
 
1、除了某一个同学外,其余所有同学向前走一步(向前走一步可理解为 $ Y_i $ 的 值加 $ 1 $ ,下同);
2、除了某一个同学外,其余所有同学向前走两步;
3、除了某一个同学外,其余所有同学向前走五步。
 
教官希望他能以最少的命令次数使得所有同学站成一条直线,但是他不会算, 于是就让亮亮帮他来计算。
亮亮虽然聪明,可是由于班上同学人数众多,他一下子也解决不了这个问题,只能来寻求会编程的你的帮助,你能告诉亮亮答案吗?
 

【输入格式】

第一行有一个整数 $ n $ ,表示班上共有 $ n $ 位同学。
第二行有 $ n $ 个整数,第 $ i $ 个整数 $ Y_i $ 表示第 $ i $ 位同学初始位置的纵坐标。
 

【输出格式】

一个整数,表示最少的下达命令次数。
 

【样例输入】

 4
 1 1 2 6

【样例输出】

 2

 

【样例解释】

一种方案是:$ 1126 → 2227 → 7777 $
 

【数据规模】

对于 $ 40 $ %的数据,$ n le 10, Y_i le 10 $ ;
对于 $ 100 $ %的数据,$ 1 le n le 100000,0 le Y_i le 1,000,000,000 $ 。
 

【题解】

算法一:当数据规模很小时,可以采用记忆化搜索或者暴力动态规划的方法。
期望得分:$ 40 $
 
算法二:我们发现,排成一条直线是一个相对的概念(它只与相对位置有关,而与绝对位置 无关),
因此如果一个人不动,其余同学都向前走 $ A $ 步,相当于其他同学都不动,而此人向 后走 $ A $ 步。
那么三种指令对应于指定 $ i $,令 $ Y_i $ 的值减 $ 1 $ ,减 $ 2 $ ,或减 $ 5 $ 。
如果我们将所有 $ Y_i $ 从小到大进行排序,那么最终所有 $ Y_i $ 都应该减少至 $ Y_1 -1, Y_1-2,...,Y_1 - 4 $ 这五个值之一。
那么我们枚举所有的 $ Y_i $ 该减少到哪一个值,计算出每一个 $ Y_i $ 减少到该值所需的指令数
(这很容易计算,先减五,减不动了减二,再减不动了减一 即可)并求和,最终比较减少到哪一个值所用指令数最少,即为所求答案。
时间复杂度:$ O(n imes log_n) $ 或 $ O(n) $
期望得分:$ 100 $
 

【代码】

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

int n, a[100001], b[100001];
long long ans = 0, ss;

int main()
{
	freopen("queue.in", "r", stdin);
	freopen("queue.out", "w", stdout);
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
	    scanf("%d", &a[i]);
	sort(a + 1, a + n + 1);
	for (int i = n; i >= 1; i--)
	    a[i] -= a[1];
	memcpy(b, a, sizeof(b));
	for (int j = 0; j < 5; j++)
	{
		ans = 0;
		memcpy(a, b, sizeof(b));
		for (int i = 1; i <= n; i++)
		    a[i] += j;
	    for (int i = 1; i <= n; i++)
	    {
	     	if (a[i] >= 5) ans += (long long)a[i]/5, a[i] %= 5;
		    if (a[i] >= 2) ans += (long long)a[i]/2, a[i] %= 2;
		    if (a[i] > 0) ans += (long long)a[i], a[i] = 0;
	    }
	    if (j == 0) ss = ans; else ss = min(ss, ans);
    }
	printf("%lld
", ss);
	return 0;
}
原文地址:https://www.cnblogs.com/PotremZ/p/Test20180914.html