• 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;
    }
    
  • 相关阅读:
    标准函数头部注释
    排序
    #define _INTSIZEOF(n)
    并发编程资料
    memory model
    Ubuntu搜狗输入法的使用
    gprof
    xml_editor
    创建本地Ubuntu镜像
    设计模式9:建造者模式
  • 原文地址:https://www.cnblogs.com/PotremZ/p/Test20180914.html
Copyright © 2020-2023  润新知