POJ算法入门—枚举

简介

  • 基于逐个尝试答案的一种问题求解策略

例题——完美立方

给一个n,找到n以内的所有a3=b3+c3+d3

#include<iostream>
#include<algorithm>
using namespace std;
using ll=long long;
ll V3(int n){
	return n*n*n;
}
int main(){
	int n;
	cin>>n;
	ll num[n+1];
	for(int i=1;i<=n;i++){
		num[i]=V3(i);//比较下层反复计算,消耗一些空间换时间
	}
	int a,b,c,d;
	for(a=3;a<=n;a++){
		for(b=2;b<=a-1;b++){
			for(c=b;c<=a-1;c++){
				for(d=c;d<=a-1;d++){
					if(num[a]==num[b]+num[c]+num[d]){
						cout<<"Cube = "<<a<<",Triple = ("<<b<<","<<c<<','<<d<<")";
						cout<<endl;
					}
                    //注意这里四个数的取值范围,某一些范围可人为控制认为不可能,进而为程序提高效率
				}
			}
		}
	}
}

例题——生理周期

#include<iostream>
#include<algorithm>
using namespace std;
int main(){
	int p,e,i,d;
	while(cin>>p>>e>>i>>d&&p!=-1){
		for(int a=33+i;a<=21252;a+=33){
            //用这个头可以实现跳着枚举这个日子,使效率更高
			if((a-e)%28==0&&(a-p)%23==0&&a>d){
				cout<<a-d<<endl;
			}
		}
	}
}
/*
while (cin>>p>>e>>i>>d&&p!=-1)
	{
		int k;
		for(k=d+1;(k-p)%23;k++);//直到k是23的倍数就停止,而且保证肯定大于d
		for(;(k-e)%28;k+=23);//保证是23的倍数,跳着找,找28倍数那一天
		for(;(k-i)%33;k+=23*28);//保证是23和28的倍数,跳着找,找三个数倍数那一天
	}
*/

例题——称硬币

/*已知三次称量结果,找出那枚假硬币
在枚举这一块是,先假设假币是更重还是更轻,再假设每一块币是假的时候,
是否符合三次测量结果,如果不符合,则下一个;*/
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
char leftstr[3][7];
char rightstr[3][7];
char resultstr[3][7];
bool IsFake(char c,bool IsHeavy){
    //IsHeavy为真时,是假设假币更重
    char *pleft,*pright;
    for(int i=0;i<3;i++){
        if(IsHeavy){
            pleft=leftstr[i];
            pright=rightstr[i];
        }else{
            pleft=rightstr[i];
            pright=leftstr[i];
        }
        switch(resultstr[i][0]){
            case 'u'://即右边比较轻,右边是up,重时假币在左边
                    if(strchr(pleft,c)==NULL){
                        return false;//即不符合,不在左边,这个币是假币不成立
                    }
                    break;
            case 'd':
                    if(strchr(pright,c)==NULL){
                        return false;
                    }
                    break;
            case 'e':
                    if(strchr(pright,c)||strchr(pleft,c)){
                        return false;
                    }
                    break;
        }
    }
    return true;
}
int main(){
	int time;
    cin>>time;
    while(time--){
        for(int i=0;i<3;i++){
            cin>>leftstr[i]>>rightstr[i]>>resultstr[i];
        }
        for(char c='A';c<='A'+12;c++){
            if(IsFake(c,true)){
                cout<<c<<"is fake.and it's heavy!"<<endl;
            }else if (IsFake(c,false)){
                cout<<c<<"is fake.and it's light!"<<endl;
            }
        }
    }
}

例题——熄灯问题

  • 用二进制数进行枚举的巧妙

  • 因为开关灯的状态都是用1,0表示,用二进制可以达到一个缩减空间的作用

    用6个比特就能存一行的灯

    用一个int数,就能把一行里的所有可能表示完;而不用使用6个循环,反复变换1,0状态

/*
基本思路:如果存在某个局部,一旦这个局部的状态被确定,那么剩余其他部分的状态只能是确定的一种,
或者是为数不多的n种,那么就只需枚举这个局部的状态即可。
异或是:相同为0,不同为1
*/
#include<iostream>
#include<cstring>
using namespace std;
char oriLights[5];//一个元素对应一行的比特情况
char lights[5];//变化中灯的矩阵
char result[5];
//在这种情况下,所有操作都是对应着一个字符的比特进行
int GetBit(char c,int i){
    return (c>>i) & 1;
    //c<<i能使第i位比特数,移动到第0位
}//能够取字符c的第i个比特
void SetBit(char &c,int i,int v){
    if(v){
        c |= (1<<i);
    }else{
        c &= ~(1<<i);
    }
}//能够把字符c的第i位比特,更改为v
void FlipBit(char &c,int i){
    c ^= (1<<i);
}//能够把字符c的第i位比特翻转,0->1;1->0
void OutPutResult(int t,char result[]){
    cout<<"PUZZLE #"<<t<<endl;
    for(int i=0;i<5;i++){
        for(int j=0;j<6;j++){
            cout<<GetBit(result[i],j);
            if(j<5){
                cout<<" ";
            }
        }
        cout<<endl;
    }
}//输出结果
int main(){
    int time;
    cin>>time;
    for(int t=1;t<=time;t++){
        memset(oriLights,0,sizeof(oriLights));
        for(int i=0;i<5;i++){
            for(int j=0;j<6;j++){
                int s;
                cin>>s;//s表示第i行第j列比特的状态0,1
                SetBit(oriLights[i],j,s);       
            }
        }//所有数据读入
        //接下来枚举第一行的所有可能状态,用0~2^6-1的int值表示
        for(int n=0;n<64;n++){//这个n使操作的可能排列,不是灯状态的排列
            char switchs=n;//switchs就表示上一行的操作状态
            memcpy(lights,oriLights,sizeof(oriLights));
            for(int i=0;i<5;i++){
                result[i]=switchs;//会自动将int转换成char再转存进去
                for(int j=0;j<6;j++){
                    if(GetBit(switchs,j)){//如果在j比特的位置进行了操作,为1,则影响周围灯状态
                        if(j>0) FlipBit(lights[i],j-1);
                        FlipBit(lights[i],j);
                        if(j<5) FlipBit(lights[i],j+1);
                    }//对同一i行的灯进行了处理
                }
                if(i<4) lights[i+1] ^= switchs;
                //这一步就能把i+1的情况更改完成
                //根据上一行灯的情况,更改下一行操作的情况;
                //下一行的操作switchs必须关掉上一行还开着的灯
                switchs=lights[i];
                //上一行灯是1的状态,则下一行操作也必须是1进行反转
            }
            //最终能不能成功,取决于最后一行能不能全灭
            if(lights[4]==0){
                OutPutResult(t,result);
                break;
            }
        }
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Caaaaaan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值