算法基础学习笔记-1-枚举

1.枚举

1.1 完美立方

形如a3>= b3 + c3+ d3的等式被称为完美立方等式。例如123= 63 + 83+ 103。编写一个程序,对任给的正整数N(N≤100),寻找所有的四元组(a,b,c,d),使得a3=b3 + c3+ d3,其中a,b,c,d 大于1,小于等于N,且b<=c<=d。

输入

一个正整数N (N≤100)。

输出

每行输出一个完美立方。输出格式为:

Cube = a,Triple = (b,c, d),

其中a,b,c,d所在位置分别用实际求出四元组值代入。

请按照a的值,从小到大依次输出。当两个完美立方等式中a的值相同,则b值小的优先输出、仍相同则c值小的优先输出、再相同则d值小的先输出。

  • 解题思路

    四重循环枚举a,b,c,d,a在最外层,d在最里层,每一层都是从小到大枚举

    a枚举范围[2,N]
    b范围[2, a-1]

  • #include <iostream>
    #include <cstdio>
    using namespace std;
    
    int main()
    {
    	int N;
    	scanf("%d", &N);
    	for (int a = 2; a <= N; ++a)
    		for (int b = 2; b < a; ++b)
    			for (int c = b; c < a; ++c)
    				for (int d = c; d < a; ++d)
    					if (a * a * a == b * b * b + c * c * c + d * d * d)
    						printf("Cube = %d, Triple = (%d,%d,%d)\n",a,b,c,d);
    	return 0;
    }
    
1.2 生理周期

人有体力、情商、智商的高峰日子,它们分别每隔23天、28天和33天出现一次。对于每个人,我们想知道何时三个高峰落在同一天。给定三个高峰出现的日子p,e和i (不一定是第一次高峰出现的日子),再给定另一个指定的日子d,你的任务是输出日子d之后,下一次三个高峰落在同一天的日子(用距离d的天数表示)。例如:给定日子为10,下次出现三个高峰同一天的日子是12,则输出2。

输入

四个整数: p,e,i和d。p, e,i分别表示体力、情感和
智力高峰出现的日子。d是给定的日子,可能小于p,e或i。所有给定日子是非负的并且小于或等于365,所求的日子小于或等于21252。

输出

从给定日子起,下一次三个高峰同一天的日子的天数)。

  • 解题思路

从d+1天开始,一直试到第21252天,对其中每个日期k,看是否满足(k-p)%23 == 0&&(k-e)%28 = =0&&(k-i)%33 == 0

如何试得更快?

找到一个周期后,寻找下一个周期每次枚举增加的天数为已找到的周期(的最小公倍数)。

  • #include <iostream>
    #include <cstdio>
    using namespace std;
    #define N 21252
    int main()
    {
    	int p, e, i, d, caseNo = 0;
    	while (cin >> p >> e >> i >> d && p != -1)
    	{
    		++caseNo;
    		int k;
    		for (k = d + 1; (k - p) % 23; ++k);
    		for (; (k - e) % 28; k += 23);
    		for (; (k - i) % 33; k += 23 * 28);
    		cout << "Case "<< caseNo <<" : the next triple peak occurs in "<< k-d <<"";
    	}
    	return 0;
    }
    
1.3 称硬币

有12枚硬币。其中有11枚真币和1枚假币。假币和真币重量不同,但不知道假币比真币轻还是重。现在,用一架天平称了这些币三次,告诉你称的结果,请你找出假币并且确定假币是轻是重(数据保证一定能找出来)。

输入

1 注意:天平左右的硬币数总是相等的,这里的up/down指右边翘起/下沉

ABCD EFGH even

ABCI EFJK up

ABIJ EFGH even

输出

K is the counterfeit coin and it is light.

  • 解题思路
    对于每一枚硬币先假设它是轻的,看这样是否符合,称量结果。如果符合,问题即解决。如果不符合,就假设它是重的,看是否符合称量结果。把所有硬币都试一遍,一定能找到特殊硬币。

    如何判断一个假设结果为真?

  • #include <iostream>
    #include <cstring>
    using namespace std;
    char Left[3][7];   //天 平左边硬币
    char Right[3][7];  //天 平右边硬币
    char result[3][7]; //结果
    bool IsFake(char c, bool light);
    //light为真表示假设假币为轻,否则表示假设假币为重
    
    int main()
    {
    	int t;
    	cin >> t;
    	while (t--)
    	{
    		for (int i = 0; i < 3; ++i)
    			cin >> Left[i] >> Right[i] >> result[i];
    		for (char C = 'A'; C <= 'L'; C++)
    		{
    			if (IsFake(C, true))
    			{
    				cout << C << " is the counterfeit coin and it is light.\n";
    				break;
    			}
    			else if (IsFake(C, false))
    			{
    				cout << C < < < " is the counterfeit coin and it is heavy. \n";
    				break;
    			}
    		}
    	}
    }
    
    bool IsFake(char c, bool light)
    //light为真表示假设假币为轻,否则表示假设假币为重
    {
    	for (int i = 0; i < 3; ++i)
    	{
    		char *
    			pLeft,
    			*pRight; //指向天平两边的字符串
    		if (light)
    		{
    			pLeft = Left[i];
    			pRight = Right[i];
    		}
    
    		else
    		{ // 如果假设假币是重的,则把称量结果左右对换
    			pLeft = Right[i];
    			pRight = Left[i];
    		}
    		switch (result[i][0])
    		{ //天平右边的情况
    		case 'u':
    			if (strchr(pRight, c) == NULL)//天平右边翘起说明为轻的假币在右边
    				return false;
    			break;
    		case 'e':
    			if (strchr(pLeft, c) == strchr(pRight, c))//天平平衡翘起说明为轻的假币左右两边都不能在
    				return false;
    			break;
    		case 'd':
    			if (strchr(pLeft, c) == NULL)//天平右边下沉说明为轻的假币在左边
    				return false;
    			break;
    		}
    	}
    	return true;
    }
    
    
1.4 熄灯问题(一)

有一个由按钮组成的矩阵,其中每行有6个按钮,共5行每个按钮的位置上有一盏灯
当按下一个按钮后,该按钮以及周围位置(上边,下边,左边,右边)的灯都会改变状态

-如果灯原来是点亮的,就会被熄灭

-如果灯原来是熄灭的,则会被点亮

在矩阵角上的按钮改变3盏灯的状态

在矩阵边上的按钮改变4盏灯的状态.

其他的按钮改变5盏灯的状态

与一盏灯毗邻的多个按钮被按下时,一个操作会抵消另一次操作的结果给定矩阵中每盏灯的初始状态,求一种按按钮方案,使得所有的灯都熄灭。

输入

-第一行是一个正整数N,表示需要解决的案例数

-每个案例由5行组成,每一行包括6个数字

-这些数字以空格隔开,可以是0或1

-0表示灯的初始状态是熄灭的

-1表示灯的初始状态是点亮的

输出

-对每个案例,首先输出一行,输出字符串“PUZZLE #m”,其中m是该案例的序号

-接着按照该案例的输入格式输出5行

-1表示需要把对应的按钮按下

-0表示不需要按对应的按钮

-每个数字以一个空格隔开

  • 解题思路

    第2次按下同一个按钮时,将抵消第1次按下时所产生的结果→每个按钮最多只需要按下一次

    第一想法:枚举所有可能的按钮(开关)状态,对每个状态计算一下最后灯的情况,看是否都熄灭,每个按钮有两种状态(按下或不按下),一共有30个开关,那么状态数是230,太多,会超时。

    如何减少枚举的状态数目呢?

    基本思路:如果存在某个局部,一旦这个局部的状态被确定,那么剩余其他部分的状态只能是确定的一种,或者不多的n种,那么就只需枚举这个局部的状态即可。

    经过观察,发现第1行就是这样的一个“ 局部”
    -因为第1行的各开关状态确定的情况下,这些开关作用过后,将导致第1行某些灯是亮的,某些灯是灭的
    -要熄灭第1行某个亮着的灯(假设位于第i列),那么唯一的办法就是按下第2行第i列的开关
    (因为第1行的开关已经用过了,而第3行及其后的开关不会影响到第1行)

    -为了使第1行的灯全部熄灭,第2行的合理开关状态就是唯一-的

    有没有状态数更少的做法?

    枚举第一列,状态数是2的5次方=32

    #include <memory>
    #include <string>
    #include <cstring>
    #include <iostream>
    using namespace std;
    char oriLights[5];
    char lights[5] ;
    char result[5] ;
    int GetBit (char c,int i){//取c的第i位
    	return(c>>i)&1;
    }
    void SetBit(char &c,int i ,int v){//c的第i位置为v
    	if(v)c|=(1<<i);
    	else c&=~(1<<i);
    }
    void FlipBit(char &c,int i){//c的第i位反转
    	c^=(1<<i);
    }
    void OutputResult(int t,char result[]){
    	cout<<"PUZZLE #"<<t<<endl;
    	for(int i=0;i<5;++i){
    		for(int j;j<6;++j){
    			cout<<GetBit(result[i],j);
    			if(j<5)cout<<" ";
    			else cout<<endl;
    		}
    	}
    }
    int main(){
    	int T;
    	for(int t=1;t<=T;++t){
    		for(int i=0;i<5;++i)
    			for(int j=0;j<6;++j){
    				int s;
    				cin>>s;
    				SetBit(oriLights[i],j,s);
    		}
    		for(int n=0;n<64;++n){
    			int switchs=n;
    			memcpy(lights,oriLights,sizeof(oriLights));
    			fot(int i=0;i<5;++i){
    				result[i]=switchs;//第i行的状态存进result
    				for(j=0;j<6;++j){
    					if(GetBit(switchs,j)){
    						if(j>0)FlipBit(Lighis[i],j-1);
    						FlipBit(lights[i],j);
    						if(j<5)FlipBit(lights[i],j+1);
    					}
    				}
    				if(i<5)
    					lights[i+1]^=switchs;//下一行的灯的状态用异或即可确定
    				switchs=lights[i];
    			}
    			if(lights[4]==0){//最后一行全被熄灭表示成功
    				OutputResult(t,result);
    				break;
    			}
    		}
    	}
    	return 0;
    }
    
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值