zoukankan      html  css  js  c++  java
  • 2013年蓝桥杯省赛C组笔记

    猜年龄(简单枚举)

    美国数学家维纳(N.Wiener)智力早熟,11岁就上了大学。他曾在1935~1936年应邀来中国清华大学讲学。一次,他参加某个重要会议,年轻的脸孔引人注目。于是有人询问他的年龄,他回答说:“我年龄的立方是个4位数。我年龄的4次方是个6位数。这10个数字正好包含了从0到9这10个数字,每个都恰好出现1次。

    请你推算一下,他当时到底有多年轻。通过浏览器,直接提交他那时的年龄数字。注意:不要提交解答过程,或其它的说明文字。

    代码:

    #include<iostream>
    using namespace std;
    int main(){
    	for(int i=10;i<50;i++){
    		int _3=i*i*i;
    		int _4=_3*i;
    		if(_3>=1000&&_3<10000&&_4>=100000&&_4<1000000){
    			printf("%d %d %d
    ",i,_3,_4);
    		}
    	}
    	return 0;
    }
    //输出结果为:
    //18 5832 104976
    //19 6859 130321
    //20 8000 160000
    //21 9261 194481
    

    由于这是填空题,只要能算出结果即可,先把可能的结果输出,根据判断年龄为18的时候,0-9每个数字只出现1次符合条件。

    马虎的算式(枚举+验证)

    小明是个急性子,上小学的时候经常把老师写在黑板上的题目抄错了。

    有一次,老师出的题目是:36 x 495 = ?

    他却给抄成了:396 x 45 = ?

    但结果却很戏剧性,他的答案竟然是对的!!

    因为 36 * 495 = 396 * 45 = 17820

    类似这样的巧合情况可能还有很多,比如:27 * 594 = 297 * 54

    假设 a b c d e 代表1~9不同的5个数字(注意是各不相同的数字,且不含0

    能满足形如: ab * cde = adb * ce 这样的算式一共有多少种呢?

    请你利用计算机的优势寻找所有的可能,并回答不同算式的种类数。

    满足乘法交换律的算式计为不同的种类,所以答案肯定是个偶数。

    答案直接通过浏览器提交。
    注意:只提交一个表示最终统计种类数的数字,不要提交解答过程或其它多余的内容。

    代码:

    暴力枚举列出所有可能结果

    #include<iostream>
    using namespace std;
    int main(){
    	int ans=0;
    	for(int a=1;a<=9;a++){
    		for(int b=1;b<=9;b++){
    			if(b!=a){//各个数字不相同判断 
    				for(int c=1;c<=9;c++){
    					if(c!=a&&c!=b){//各个数字不相同判断 
    						for(int d=1;d<=9;d++){
    							if(d!=a&&d!=b&&d!=c){//各个数字不相同判断 
    								for(int e=1;e<=9;e++){
    									if(e!=a&&e!=b&&e!=c&&e!=d){//各个数字不相同判断 
    										if((a*10+b)*(c*100+d*10+e)==(a*100+d*10+b)*(c*10+e)){
    											ans++;
    										}
    									}
    								}
    							}
    						}
    					}
    				}
    			}
    		}
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    //输出结果:142
    

    暴力到没朋友

    填空题应第一考虑能否暴力枚举,简单粗暴。

    振兴中华(递归)

    小明参加了学校的趣味运动会,其中的一个项目是:跳格子。

    地上画着一些格子,每个格子里写一个字,如下所示:

    比赛时,先站在左上角的写着“从”字的格子里,可以横向或纵向跳到相邻的格子里,但不能跳到对角的格子或其它位置。一直要跳到“华”字结束。

    要求跳过的路线刚好构成“从我做起振兴中华”这句话。

    请你帮助小明算一算他一共有多少种可能的跳跃路线呢?

    答案是一个整数,请通过浏览器直接提交该数字。
    注意:不要提交解答过程,或其它辅助说明类的内容。

    代码:

    简单理解就是从“从”到“华”有多少条路可以走

    #include<iostream>
    using namespace std;
    bool vis[5][5];
    int f(int x,int y){
    	if(x==3&&y==4) //已经到"华"只有一种
    		return 1;
    	else if(x==3) //已经到最后一行,只能往右走
    		return f(x,y+1);
    	else if(y==4) //已经到最后一列,只能往下走
    		return f(x+1,y);
    	else
    		return f(x+1,y)+f(x,y+1);
    	
    }
    int main(){
    	cout<<f(0,0);
    	return 0;
    }
    //输出结果:35
    

    递归的方法:找重复、找状态、找出口

    幻方填空(暴力法全排列)

    幻方是把一些数字填在方格中,使得行、列、两条对角线的数字之和都相等。

    欧洲最著名的幻方是德国数学家、画家迪勒创作的版画《忧郁》中给出的一个4阶幻方。

    他把1,2,3,...16 这16个数字填写在4 x 4的方格中。

    如图所示,即:

    表中有些数字已经显露出来,还有些用?和*代替。

    请你计算出? 和 * 所代表的数字。并把 * 所代表的数字作为本题答案提交。

    答案是一个整数,请通过浏览器直接提交该数字。

    注意:不要提交解答过程,或其它辅助说明类的内容。

    代码:

    已经使用的数字:1,9,11,13,16

    未使用的数字:2,3,4,5,6,7,8,10,12,14,15

    因为每个空只能填一个数字,每个数字只能填一次,故使用暴力法,求未使用的数字进行全排列,即它们可能组成的各种顺序,依次填入方格中进行判断,当满足条件时返回即可。

    在c++中有相应的全排列公式next_permutation(),其它语言需要手动实现。

    #include<iostream>
    #include<vector>
    #include<algorithm> 
    using namespace std;
    int a[]={2,3,4,5,6,7,8,10,12,14};
    
    void check(vector<int> arr){
    	int r1=16+arr[0]+arr[1]+13;
    	int r2=arr[2]+arr[3]+11+arr[4];
    	int r3=9+arr[5]+arr[6]+arr[7];
    	int r4=arr[8]+15+arr[9]+1;
    	
    	int c1=16+arr[2]+9+arr[8];
    	int c2=arr[0]+arr[3]+arr[5]+15;
    	int c3=arr[1]+11+arr[6]+arr[9];
    	int c4=13+arr[4]+arr[7]+1;
    	
    	int l=16+arr[3]+arr[6]+1;
    	int r=13+11+arr[5]+arr[8];
    	if(r1==r2&&r2==r3&&r3==r4&&r4==c1&&c1==c2&&c2==c3&&c3==c4&&c4==l&&l==r){
    		cout<<arr[7]<<endl;
    	}
    }
    
    int main(){
    	vector<int> arr;
    	for(int i=0;i<10;i++){
    		arr.push_back(a[i]);
    	}
    	do{
    		check(arr); 
    	}while(next_permutation(arr.begin(),arr.end()));
    	return 0;
    }
    

    公约数公倍数

    我们经常会用到求两个整数的最大公约数和最小公倍数的功能。
    下面的程序给出了一种算法。
    函数 myfunc 接受两个正整数a,b
    经过运算后打印出 它们的最大公约数和最小公倍数。
    此时,调用 myfunc(15,20)

    将会输出:
    5
    60

    请分析代码逻辑,并推测划线处的代码,通过网页提交。
    注意:仅把缺少的代码作为答案,千万不要填写多余的代码、符号或说明文字!!

    // 交换数值
    void swap(int *a,int *b)
    {
       int temp;
       temp=*a;
       *a=*b;
       *b=temp;
    }
    
    void myfunc(int a, int b)  //使用辗转相除法求最大公约数
    {
       int m,n,r;  
       if(a<b) swap(&a,&b);
       m=a;n=b;r=a%b;
       while(r!=0)
       {
        a=b;b=r;
        r=a%b;
       }
       printf("%d
    ",b);  // 最大公约数 
       printf("%d
    ", ____________________________________);  // 最小公倍数 
    }
    //已知最大公约数b时可以使用公式 n*m/b     a,b已经改变,n,m是a,b的拷贝故使用n,m.
    //答案:n*m/b
    

    三部排序(快速排序的变体)

    一般的排序有许多经典算法,如快速排序、希尔排序等。

    但实际应用时,经常会或多或少有一些特殊的要求。我们没必要套用那些经典算法,可以根据实际情况建立更好的解法。

    比如,对一个整型数组中的数字进行分类排序:

    使得负数都靠左端,正数都靠右端,0在中部。注意问题的特点是:负数区域和正数区域内并不要求有序。可以利用这个特点通过1次线性扫描就结束战斗!!

    以下的程序实现了该目标。

    其中x指向待排序的整型数组,len是数组的长度。

    如果给定数组:
    25,18,-2,0,16,-5,33,21,0,19,-16,25,-3,0
    则排序后为:
    -3,-2,-16,-5,0,0,0,21,19,33,25,16,18,25

    请分析代码逻辑,并推测划线处的代码,通过网页提交
    注意:仅把缺少的代码作为答案,千万不要填写多余的代码、符号或说明文字!!

    void sort3p(int* x, int len)
    {
        int p = 0;
        int left = 0;    
        int right = len-1;
    
        while(p<=right){
            if(x[p]<0){
                int t = x[left];
                x[left] = x[p];
                x[p] = t;
                left++;
                p++;
        	}else if(x[p]>0){
                int t = x[right];
                x[right] = x[p];
                x[p] = t;
                right--;
            }
            else{
            	p++;  //填空位置
            }
    	}
    }
    

    p 指向当前要判断元素的下边

    left 指向元素的左边都小于0,right指向元素的右边都大于0。

    核桃的数量(简单枚举)

    问题描述
    小张是软件项目经理,他带领3个开发组。工期紧,今天都在加班呢。为鼓舞士气,小张打算给每个组发一袋核桃(据传言能补脑)。他的要求是:

    1. 各组的核桃数量必须相同
    2. 各组内必须能平分核桃(当然是不能打碎的)
    3. 尽量提供满足1,2条件的最小数量(节约闹革命嘛)

    输入格式
    输入包含三个正整数a, b, c,表示每个组正在加班的人数,用空格分开(a,b,c<30)
    输出格式
    输出一个正整数,表示每袋核桃的数量。
    样例输入1
    2 4 5
    样例输出1
    20
    样例输入2
    3 1 1
    样例输出2
    3

    代码:

    简单理解就是要求这三个数的最小公倍数,使用暴力枚举,最坏三者的最小公倍数为a * b * c。

    #include<iostream>
    using namespace std;
    int main(){
    	int a,b,c;
    	cin>>a>>b>>c;
    	for(int i=1;i<=a*b*c;i++){
    		if(i%a==0&&i%b==0&&i%c==0){
    			cout<<i<<endl;
    			break;
    		}
    	}
    	return 0;
    } 
    

    打印十字图(先写死再写活)

    小明为某机构设计了一个十字型的徽标(并非红十字会啊),如下所示:

    ..$$$$$$$$$$$$$..
    ..$...........$..
    $$$.$$$$$$$$$.$$$
    $...$.......$...$
    $.$$$.$$$$$.$$$.$
    $.$...$...$...$.$
    $.$.$$$.$.$$$.$.$
    $.$.$...$...$.$.$
    $.$.$.$$$$$.$.$.$
    $.$.$...$...$.$.$
    $.$.$$$.$.$$$.$.$
    $.$...$...$...$.$
    $.$$$.$$$$$.$$$.$
    $...$.......$...$
    $$$.$$$$$$$$$.$$$
    ..$...........$..
    ..$$$$$$$$$$$$$..
    

    对方同时也需要在电脑dos窗口中以字符的形式输出该标志,并能任意控制层数。

    输入1 ,则输出

    ..$$$$$..
    ..$...$..
    $$$.$.$$$
    $...$...$
    $.$$$$$.$
    $...$...$
    $$$.$.$$$
    ..$...$..
    ..$$$$$..
    

    输入

    一个正整数 n (n< 30) 表示要求打印图形的层数。

    输出

    对应包围层数的该标志。

    样例输入

    3
    

    样例输出

    ..$$$$$$$$$$$$$.. 
    ..$...........$.. 
    $$$.$$$$$$$$$.$$$ 
    $...$.......$...$ 
    $.$$$.$$$$$.$$$.$
    $.$...$...$...$.$
    $.$.$$$.$.$$$.$.$
    $.$.$...$...$.$.$
    $.$.$.$$$$$.$.$.$
    $.$.$...$...$.$.$
    $.$.$$$.$.$$$.$.$
    $.$...$...$...$.$
    $.$$$.$$$$$.$$$.$
    $...$.......$...$
    $$$.$$$$$$$$$.$$$
    ..$...........$..
    ..$$$$$$$$$$$$$.. 
    

    代码:

    假设最外层为第一层

    n=3时

    为17 * 17的方格;第一行$个数13

    n=2时

    为13 * 13的方格; 第一行$个数9

    n=1时

    为 9 * 9的方格; 第一行$个数5

    L表示左侧边界为0,R表示右侧边界为9+4*(n-1)-1 (下标从0开始R的下标需要减1)

    写死后,只输出最外一层

    #include<iostream>
    using namespace std;
    char arr[9+4*28][9+4*28];
    int N; //一共有几层 
    int L=0; //最外层左边界下标 
    int R; //最外层右边界下标 
    void printAll(int left,int right){
    	for(int i=left;i<=right;i++){
    		for(int j=left;j<=right;j++){
    			if(arr[i][j]!='$') arr[i][j]='.';
    			cout<<arr[i][j];
    		}
    		cout<<endl;
    	}
    }
    int main(){
    	cin>>N;
    	cout<<N<<endl;
    	R=9+4*(N-1)-1; //求出最外层右边界下标 
    	
    	//处理第一行和最后一行 
    	for(int i=2;i<=R-2;i++){
    		arr[0][i]='$';
    		arr[R][i]='$';
    	} 
    	//处理第二行和倒数第二行
    	arr[1][2]='$'; arr[1][R-2]='$';
    	arr[R-1][2]='$'; arr[R-1][R-2]='$';
    	//处理第三行和倒数第三行
    	arr[2][0]='$';
    	arr[2][1]='$';
    	arr[2][2]='$';
    	arr[2][R]='$';
    	arr[2][R-1]='$';
    	arr[2][R-2]='$';
    	arr[R-2][0]='$';
    	arr[R-2][1]='$';
    	arr[R-2][2]='$';
    	arr[R-2][R]='$';
    	arr[R-2][R-1]='$';
    	arr[R-2][R-2]='$';
    	//处理两边
    	for(int i=3;i<=R-3;i++){
    		arr[i][0]='$';
    		arr[i][R]='$';
    	} 
    	printAll(L,R);
    	return 0;
    }
    输入:
    3
    输出:
    ..$$$$$$$$$$$$$..
    ..$...........$..
    $$$...........$$$
    $...............$
    $...............$
    $...............$
    $...............$
    $...............$
    $...............$
    $...............$
    $...............$
    $...............$
    $...............$
    $...............$
    $$$...........$$$
    ..$...........$..
    ..$$$$$$$$$$$$$..
    输入:
    1
    输出:
    ..$$$$$..
    ..$...$..
    $$$...$$$
    $.......$
    $.......$
    $.......$
    $$$...$$$
    ..$...$..
    ..$$$$$..
    

    然后找规律,发现每层只是左右边界不同L每次加2,R每次减2,故单独写一个方法输出第n层的图像

    #include<iostream>
    using namespace std;
    char arr[9+4*28][9+4*28];
    int N; //一共有几层 
    int L=0; //最外层左边界下标 
    int R; //最外层右边界下标 
    void printAll(int left,int right){
    	for(int i=left;i<=right;i++){
    		for(int j=left;j<=right;j++){
    			if(arr[i][j]!='$') arr[i][j]='.';
    			cout<<arr[i][j];
    		}
    		cout<<endl;
    	}
    }
    void dealN(int n){
    	int l,r; //当前层的左右边界,因为时方格,l也表示上边界,r也表示下边界 
    	l=2*(N-n);
    	r=R-2*(N-n); 
    	//处理第一行和最后一行 
    	for(int i=l+2;i<=r-2;i++){
    		arr[l][i]='$';
    		arr[r][i]='$';
    	} 
    	//处理第二行和倒数第二行
    	arr[l+1][l+2]='$'; arr[l+1][r-2]='$';
    	arr[r-1][l+2]='$'; arr[r-1][r-2]='$';
    	//处理第三行和倒数第三行
    	arr[l+2][l]='$';
    	arr[l+2][l+1]='$';
    	arr[l+2][l+2]='$';
    	arr[l+2][r]='$';
    	arr[l+2][r-1]='$';
    	arr[l+2][r-2]='$';
    	arr[r-2][l]='$';
    	arr[r-2][l+1]='$';
    	arr[r-2][l+2]='$';
    	arr[r-2][r]='$';
    	arr[r-2][r-1]='$';
    	arr[r-2][r-2]='$';
    	//处理两边
    	for(int i=l+3;i<=r-3;i++){
    		arr[i][l]='$';
    		arr[i][r]='$';
    	} 
    }
    int main(){
    	cin>>N;
    
    	R=9+4*(N-1)-1; //求出最外层右边界下标 
    	
    	for(int i=N;i>0;i--){
    		dealN(i);
    	}
    	
    	//最后处理中间十字	
    	for(int i=2*N;i<2*N+5;i++){
    		arr[2*N+2][i]='$';
    		arr[i][2*N+2]='$';
    	}
    	printAll(L,R);
    	return 0;
    }
    输入3时
    ..$$$$$$$$$$$$$..
    ..$...........$..
    $$$.$$$$$$$$$.$$$
    $...$.......$...$
    $.$$$.$$$$$.$$$.$
    $.$...$...$...$.$
    $.$.$$$.$.$$$.$.$
    $.$.$...$...$.$.$
    $.$.$.$$$$$.$.$.$
    $.$.$...$...$.$.$
    $.$.$$$.$.$$$.$.$
    $.$...$...$...$.$
    $.$$$.$$$$$.$$$.$
    $...$.......$...$
    $$$.$$$$$$$$$.$$$
    ..$...........$..
    ..$$$$$$$$$$$$$..
    

    带分数(全排列+枚举“+”号和“/”号的插入位置)

    问题描述
    100 可以表示为带分数的形式:100 = 3 + 69258 / 714。

    还可以表示为:100 = 82 + 3546 / 197。

    注意特征:带分数中,数字1~9分别出现且只出现一次(不包含0)。

    类似这样的带分数,100 有 11 种表示法。

    输入格式
    从标准输入读入一个正整数N (N<1000*1000)

    输出格式
    程序输出该数字用数码1~9不重复不遗漏地组成带分数表示的全部种数。

    注意:不要求输出每个表示,只统计有多少表示法!

    样例输入1
    100
    样例输出1
    11
    样例输入2
    105
    样例输出2
    6

    分析:

    生成1-9这9个数字的全排列,先在可能的位置插入“+”号,然后在可能的位置插入“/”号,再验证等式。

    #include<iostream>
    #include<algorithm>
    #include<string>
    using namespace std;
    int main(){
    	int n,ans=0;
    	cin>>n;
    	string s="123456789";
    	do{
    		for(int i=1;i<=7;i++){ // i:"+"前串的长度 
    			string a=s.substr(0,i);
    			int inta=atoi(a.c_str());  // string 转换 int 
    			if(inta>=n) break;
    			for(int j=1;j<=9-i-1;j++){ // j: "+"和"/"之间串的长度 
    				string b=s.substr(i,j);
    				string c=s.substr(i+j);// "/"后面的串
    				int intb=atoi(b.c_str());
    				int intc=atoi(c.c_str());
    				if(intb%intc==0&&inta+intb/intc==n) ans++;
    			} 
    		}
    	}while(next_permutation(s.begin(),s.end()));
    	cout<<ans<<endl;
    	return 0;
    }
    

    结果时正确的,但是运行超时,反复使用substr()函数会消耗大量时间,需要对代码进行改进。

    #include<iostream>
    #include<algorithm>
    #include<string>
    using namespace std;
    
    //自己实现一个函数,通过从pos截取len长度的字符串
    int parse(const char *arr,int pos,int len){
    	int ans=0;
    	int t=1;
    	for(int i=pos+len-1;i>=pos;i--){
    		ans+=(arr[i]-'0')*t;
    		t*=10;
    	}
    	return ans;
    }
    int main(){
    	int n,ans=0;
    	cin>>n;
    	string s="123456789";
    	do{
    		const char *str=s.c_str();
    		for(int i=1;i<=7;i++){ // i:"+"前串的长度 
    //			string a=s.substr(0,i);
    //			int inta=atoi(a.c_str());  // string 转换 int 
    			int inta=parse(str,0,i);
    			if(inta>=n) break;
    			for(int j=1;j<=9-i-1;j++){ // j: "+"和"/"之间串的长度 
    //				string b=s.substr(i,j);
    //				string c=s.substr(i+j);// "/"后面的串
    //				int intb=atoi(b.c_str());
    //				int intc=atoi(c.c_str());
    				int intb=parse(str,i,j);
    				int intc=parse(str,i+j,9-i-j);
    				if(intb%intc==0&&inta+intb/intc==n) ans++;
    			} 
    		}
    	}while(next_permutation(s.begin(),s.end()));
    	cout<<ans<<endl;
    	return 0;
    } 
    //完美,测评通过。
    

    剪格子(深搜+回溯+剪枝)

    如下图所示,3 x 3 的格子中填写了一些整数。

    +--*--+--+
    |10* 1|52|
    +--****--+
    |20|30* 1|
    *******--+
    | 1| 2| 3|
    +--+--+--+
    

    我们沿着图中的星号线剪开,得到两个部分,每个部分的数字和都是60。

    本题的要求就是请你编程判定:对给定的m x n 的格子中的整数,是否可以分割为两个部分,使得这两个区域的数字和相等。

    如果存在多种解答,请输出包含左上角格子的那个区域包含的格子的最小数目。

    如果无法分割,则输出 0。

    输入格式
    程序先读入两个整数 m n 用空格分割 (m,n<10)。

    表示表格的宽度和高度。

    接下来是n行,每行m个正整数,用空格分开。每个整数不大于10000。

    输出格式
    输出一个整数,表示在所有解中,包含左上角的分割区可能包含的最小的格子数目。
    样例输入1
    3 3
    10 1 52
    20 30 1
    1 2 3
    样例输出1
    3
    样例输入2
    4 3
    1 1 1 1
    1 30 80 2
    1 1 1 100
    样例输出2
    10

    #include<iostream>
    #include<algorithm>
    using namespace std;
    int m,n,total;
    int g[10][10],ans=100;
    bool vis[10][10];
    int dx[]={1, 0,-1,0};
    int dy[]={0,-1, 0,1};
    bool in(int x,int y){  //判断该位置是否合法 
    	if(x>=0&&x<=n-1&&y>=0&&y<=m-1){
    		return true;
    	}else{
    		return false;
    	}
    }
    void f(int x,int y,int sum,int cnt){
    //	printf("(%d,%d)sum=%d,cnt=%d
    ",x,y,sum,cnt);
    	if(sum>total/2)	return;
    	if(sum==total/2){
    		ans=min(ans,cnt);
    		return;
    	}
    	vis[x][y]=true;
    	for(int i=0;i<4;i++){
    		int tx=x+dx[i];
    		int ty=y+dy[i];
    		if(in(tx,ty)&&!vis[tx][ty]){
    			f(tx,ty,sum+g[x][y],cnt+1);
    		}
    	}
    	vis[x][y]=false;
    }
    int main(){
    	cin>>m>>n;
    	for(int i=0;i<n;i++){
    		for(int j=0;j<m;j++){
    			cin>>g[i][j];
    			total+=g[i][j];  //求出所有元素的和 
    		}
    	}
    	f(0,0,0,0);
    	cout<<ans<<endl;
    	return 0;
    }
    

    上面的代码可以通过蓝桥杯练习系统,但在有些特殊的情况会执行错误,例如:

    1 1

    1 2

    应输出3,运行结果是0,表示无法分割。

  • 相关阅读:
    hdu 1176 免费馅饼
    算法竞赛入门经典训练指南-做题详细记录(更新中)
    poj-3250 Bad Hair Day
    poj-2823 Sliding Window
    2019.7.15-7.20暑假集训总结
    hdu 1754 I hate it
    poj 1611 The Suspects
    hdu 3038 How Many Answers Are Wrong
    hdu 1213 How Many Tables
    POJ 1182 食物链
  • 原文地址:https://www.cnblogs.com/lxy0/p/12663103.html
Copyright © 2011-2022 走看看