蓝桥寒假训练2->2013年第四届蓝桥杯国赛

1.猜灯谜

A 村的元宵节灯会上有一迷题:

    请猜谜 * 请猜谜 = 请边赏灯边猜

小明想,一定是每个汉字代表一个数字,不同的汉字代表不同的数字。

请你用计算机按小明的思路算一下,然后提交“请猜谜”三个字所代表的整数即可。

答案:897

法一:逐一枚举所有3位数,分离各位数字,判断与商中相应数字是否相同,且要判断商中十位与万位是否相同

#include <cstdio>
#include <iostream>  
using namespace std;  
int main(){  
    int a,b,c,num;  
    for(int i=100;i<1000;i++){//请猜谜
        if(i*i>=100000){
            num=i*i;//请边赏灯边猜
            a=i/100;
            b=(i%100)/10;
            c=i%10;
            if(num/100000==a&&num%10==b&&(num%100)/10==(num/10000)%10){
                cout<<i<<endl;
            }
        }
    }
    return 0;
}  

法二:一共出现了6个不同的数字,还可用6重循环暴力破解出答案

2.连续奇数和

小明看到一本书上写着:任何数字的立方都可以表示为连续奇数的和。

比如:
2^3 = 8 = 3 + 5
3^3 = 27 = 7 + 9 + 11
4^3 = 64 = 1 + 3 + ... + 15
虽然他没有想出怎么证明,但他想通过计算机进行验证。

请你帮助小明写出 111 的立方之连续奇数和表示法的起始数字。如果有多个表示方案,选择起始数字小的方案。 


法一:枚举首项和尾项,等差数列求和

#include <cstdio>
#include <iostream>
using namespace std;
int main(){
	int a=111*111*111;//1367631
	for(int i=1;i<=a;i+=2){//首项
		for(int j=i+2;j<=a;j+=2){//尾项
			int n=(j-i)/2+1;//项数
			int sum=n*(i+j)/2;//等差数列求和
			if(a==sum){
				cout<<i<<endl;
				return 0;
			}
		}
	}
}

法二:奇数的等差序列an=2*n-1,Sn=n*n,也可以枚举首项和尾项为第几项

#include <cstdio>
#include <iostream>
using namespace std;
int main(){ 
	int a=111*111*111;
    for(int i=1;i<=2000;i++){//首项项数
    	for(int j=i;j<=2000;j++){//尾项项数
    		if(j*j-(i-1)*(i-1)==a){
    			cout<<2*i-1<<endl;
    		}
    	}
    }
    return 0;
}  


3.空白格式化

本次大赛采用了全自动机器测评系统。

如果你的答案与标准答案相差了一个空格,很可能无法得分,所以要加倍谨慎!

但也不必过于惊慌。因为在有些情况下,测评系统会把你的答案进行“空白格式化”。其具体做法是:去掉所有首尾空白;中间的多个空白替换为一个空格。所谓空白指的是:空格、制表符、回车符。


void f(char* from, char* to){
	char* p_from = from;
	char* p_to = to; 
	while(*p_from==' '||*p_from=='\t'||*p_from=='\n') 
		p_from++; 
	do{
		if(*p_from==' '||*p_from=='\t'||*p_from=='\n'){ 
			do{
				p_from++;
			}while(*p_from==' '||*p_from=='\t'||*p_from=='\n');
		    if(*p_from!='\0')//填空
		        *p_to++=' ';
        }
    }while(*p_to++=*p_from++);
}
//*p_from!='\0'

填空部分的意思,中间的多个空白替换为一个空格,并要判断是否到达’\0’,到达’\0’就不用再添加空格

4.高僧斗法

古时丧葬活动中经常请高僧做法事。仪式结束后,有时会有“高僧斗法”的趣味节目,以舒缓压抑的气氛。

节目大略步骤为:先用粮食(一般是稻米)在地上“画”出若干级台阶(表示N级浮屠)。又有若干小和尚随机地“站”在某个台阶上。最高一级台阶必须站人,其它任意。(如图1所示)

两位参加游戏的法师分别指挥某个小和尚向上走任意多级的台阶,但会被站在高级台阶上的小和尚阻挡,不能越过。两个小和尚也不能站在同一台阶,也不能向低级台阶移动。

两法师轮流发出指令,最后所有小和尚必然会都挤在高段台阶,再也不能向上移动。轮到哪个法师指挥时无法继续移动,则游戏结束,该法师认输。

对于已知的台阶数和小和尚的分布位置,请你计算先发指令的法师该如何决策才能保证胜出。

输入数据为一行用空格分开的N个整数,表示小和尚的位置。台阶序号从1算起,所以最后一个小和尚的位置即是台阶的总数。(N<100, 台阶总数<1000)

输出为一行用空格分开的两个整数: A B, 表示把A位置的小和尚移动到B位置。若有多个解,输出A值较小的解,若无解则输出-1。

例如:

用户输入:

1 5 9

则程序输出:

1 4

再如:

用户输入:

1 5 8 10

则程序输出:

1 3


法一:

以两个物体间距离为石子数来进行Nim博弈。从低阶到高阶每次取两个小和尚,和尚不重复取,他们中间的间隔即为一堆石子的数量,不过需要注意的是,这里的石子数能减少,也能增加。由博弈论Nim相关知识,若(a1^a2^a3^…..^an)!=0,一定存在着合法的移动,将ai变成aii后满足,(a1^a2^..^aii^…^an)=0 ,同时这一性质也是解决移动的方法,只要找出对一个石子堆能通过增加石子或者减少石子符合这种要求,就是一种解法。所以移动有两种情况:

一个是缩短距离,就是说两个物体间的距离减小后会到达P态(必败态)

另一个是增大距离,也就是右边的物体在允许的情况下向右移动一定位置后到达P态(必败态)

#include <cstdio>
#include <iostream>
using namespace std;
int main(){
	int count=0,top=0,sum=0;
	int a[105],b[105];
	//读取小和尚位置
	while(scanf("%d",&a[count++])!=EOF);
	count--;
	//构建"石子堆",每次取两个小和尚的位置。若和尚数为奇数个,则最后一个多出的小和尚一定位于最高级台阶且不能移动,所以对移动没影响,可以不用理会
	for(int i=0;i+1<count;i+=2){
		b[top++]=a[i+1]-a[i]-1;
	}
	for(int i=0;i<top;i++){
		sum^=b[i];
	}
	if(!sum){
		cout<<-1<<endl;
		return 0;
	}
	//b[i]能与sum异或变小,说明可以通过减少此堆石子使N态转化为P态
	//b[i]能与sum异或变大且b[i]变大在下一石子堆允许的情况下,说明可以通过增加此堆石子使N态转化为P态
	for(int i=0;i<top;i++){//找第一个能使N态转化为P态的石子堆
		if((sum^b[i])<=b[i]){//减少石子
			printf("%d %d\n",a[i*2],a[i*2]+b[i]-(sum^b[i]));
			break;
		}
		else if((sum^b[i])>b[i]&&a[i*2+1]+(sum^b[i])-b[i]<a[i*2+2]){//增加石子
			printf("%d %d\n",a[i*2+1],a[i*2+1]+(sum^b[i])-b[i]);
			break;
		}
	}
	return 0;
}

法二:

和尚是台阶数,每两人间隔的距离是石子数,转化为正常阶梯博弈的模型,取奇数石子堆进行Nim博弈。对于每个和尚,均枚举可以移动的距离,当偶数堆减少,相当于相邻的奇数堆增加,这也与法一直接取的奇数堆能增加或减少相同,只不过法一没有枚举可以增大或减少多少,而是通过异或操作判断能不能利用当前石子堆达到P态。对于阶梯博弈取奇数台阶进行Nim博弈的正确性,网上有相关证明。

#include <stdio.h>
int main(){
	int a[105],b[105],i=0,j,k,count,sum=0;
	char c;
	while(1){
		scanf("%d%c",&a[i++],&c);
		if(c=='\n')
			break;
	}
	count=i;
	for(i=0;i<count-1;i++){//和尚是台阶,两人间隔的距离是石子数
		b[i]=a[i+1]-a[i]-1;
	}
	b[count-1]=0;
	for(i=0;i<count;i=i+2){
		sum^=b[i];
	}
	if(sum==0){
		printf("-1\n");
	}
	else{
		for(i=0;i<count;i++){
			for(j=1;j<=b[i];j++){
				b[i]-=j;//枚举所有能拿的方案
				if(i!=0){
					b[i-1]+=j;
				}
				sum=b[0];
				for(k=2;k<count;k=k+2){
					sum^=b[k];
				}
				if(sum==0){
					printf("%d %d\n",a[i],a[i]+j);
					break;
				}
				b[i]+=j;
				if(i!=0){
					b[i-1]-=j;
				}
			}
		}
	}
	return 0;
}


5.格子刷油漆

X国的一段古城墙的顶端可以看成 2*N个格子组成的矩形(如图1所示),现需要把这些格子刷上保护漆。

你可以从任意一个格子刷起,刷完一格,可以移动到和它相邻的格子(对角相邻也算数),但不能移动到较远的格子(因为油漆未干不能踩!)

比如:a d b c e f 就是合格的刷漆顺序。

c e f d a b 是另一种合适的方案。

当已知 N 时,求总的方案数。当N较大时,结果会迅速增大,请把结果对 1000000007 (十亿零七) 取模。

输入数据为一个正整数(不大于1000)

输出数据为一个正整数。

例如:

用户输入:

2

程序应该输出:

24

再例如:

用户输入:

3

程序应该输出:

96

再例如:

用户输入:

22

程序应该输出:

359635897


此题若用深搜做,会超时。最佳方法是动态规划。

刷油漆可分两种情况:

一种是从端点(以左端点为例)出发,从端点出发又分①向右走不回头②先走同一列的,再随机走③先向右走,然后折回来,再向右走。

另一种是从中间出发,开始显然不能直接往下走,否则无法遍历所有点,应当是先遍历左边(右边)所有点,然后回到相对的点,然后遍历右边(左边)的点。注意先遍历的时候,必须是遍历全体格子后回到与之相对的格子,而后遍历则不受限制。设从第i列开始出发,出发点有两种选法,第一落脚点又有两种走法,后遍历的第一落脚点又有两种走法。

#include <stdio.h>
long long a[1001],b[1001],sum;
//a[i]存储的是i列格子刷油漆的所有方案,b[i]存储的是向一个方向走不回头的方案数
#define NUM 1000000007
int main() {
	int i,n;
	scanf("%d",&n);
	b[1]=1;
	//就是去储存一下2的n-1次方
	for (i=2; i<=n; i++)
		b[i]=(b[i-1]*2%NUM);  
	/*
	完全也可以用快速幂直接算
	long long Quick_Pow(int n){
		n--;
		long long a = 2,ans = 1;
		while(n > 0){
			if(n & 1){
				ans = (ans * a) % num;
			}
			n /= 2;
			a = (a * a) % num;
		}
		return ans % num;
	}*/

	a[1]=1;
	a[2]=6;
	for (i=3; i<=n; i++)
		a[i]=(2*a[i-1]+b[i]+4*a[i-2])%NUM;
	//b[i] 向右走不回头
	//2*a[i-1] 先走同一列的,再随机走
	//4*a[i-2] 先向右走,然后折回来,再向右走

	sum=4*a[n];  //上述的一个端点的情况。因此要乘以4
	for (i=2; i<n; i++) //中间的
		sum=((sum+8*b[n-i]*a[i-1]%NUM)%NUM+(8*a[n-i]*b[i-1])%NUM)%NUM;
// 出发点在中间的时候,
//显然不能直接往下走,
//否则无法遍历所有点,
//应当是先遍历左边(右边)所有点,
//然后回到相对的点,然后遍历右边(左边)的点。
//注意先遍历的时候,必须是遍历全体格子后回到与之相对的格子的走法,
//否则无法遍历出发点正下方的点,而后遍历则不受限制。
//因此设从第i列开始出发,出发点有两种选法,
//第一落脚点又有两种走法,后遍历的第一落脚点又有两种走法,
//走完总走法数为  2*(2*b[i-1]*2*a[n-i])+2*(2*b[n-i]*2*a[i-1])
//即sum=((sum+8*b[n-i]*a[i-1]%NUM)%NUM+(8*a[n-i]*b[i-1])%NUM)%NUM;
	if(i == 1)	sum = 2;
	printf("%lld\n",sum);
	return 0;
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值