Fork me on GitHub

算法基础(一):枚举

枚举

基于逐个尝试答案的一种问题求解策略,解决问题时最先想到的搜索方法。

Problem1:假币问题

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

输入:第一行是测试数据组每组数据有三行,每行表示一次称量的结果。硬币A-L。称量结果分为三部分:天平左边放置的硬币 天平右边放置的硬币 平衡状态(up、down、even右相对于左) 左右硬币数相同。
输出:输出哪一个标号的硬币是假,并说明轻还是重
解题思路:枚举每一枚硬币是轻/重,看是否符合称量结果。
注:strchr函数功能为在一个串中查找给定字符的第一个匹配之处。函数原型为:char *strchr(const char *str, int c),即在参数 str 所指向的字符串中搜索第一次出现字符 c(一个无符号字符)的位置。strchr函数包含在C 标准库 <string.h>中。

#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;
            }
        }
    }
    return 0;
}
bool IsFake(char c,bool 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;
        }
    }
}

Problem2: 熄灯问题 POJ 1222

题目:5*6的灯矩阵,输入状态(1代表开,0代表灭),输出开关方案(1代表按一次开关)使得所有灯熄灭,开关按下后,四周的灯都会改变状态。

思路:每个状态都试一下,2^30次,肯定超时 。想办法缩小枚举范围!
考虑能否用枚举局部:局部确定,剩余部分确定。
枚举第一行,状态是2^6=64! -——>对应剩余行确定!(只有第二列才能改变第一列的灯,以此类推)
这里介绍一种非常好用的二进制枚举方法枚举一个k位二进制数的每一位变化,只需要对应一个int型变量从0变到2^k-1进行枚举
即一行灯对应一个0-63的数字,对每一盏灯的操作即进行位运算操作
为了节省空间,采用char类型变量储存这个数字(只有8个比特)

技巧:异或的逻辑:跟0异或不变,跟1异或取反。

具体代码如下:

#include<iostream>
#include<string>
#include<cstring>
#include<memory>
using namespace std;
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 Flip(char& c,int i){
    //将c的第i位取反 异或(跟0异或不变,跟1异或反转)
    c ^= (1<<i);
}
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(){
    char oriLights[5]; //最初灯矩阵,一个比特表示一盏灯
    char lights[5]; //不停变化的灯矩阵
    char result[5]; //结果开关矩阵
    char switchs; //某一行的开关状态
    int T;
    cin>>T;
    for (int t=1;t<=T;++ t){
        memset(oriLights,0,sizeof(oriLights));
        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){//遍历首行开关的64种状态
            memcpy(lights,oriLights,sizeof(oriLights));
            switchs=n;//第i行的开关状态
            for(int i=0;i<5;++i){
                result[i]=switchs;//第i行的开关方案
                for(int j=0;j<6;j++){
                    if(GetBit(switchs,j)){
                        if(j>0)
                            Flip(lights[i],j-1);//改左灯
                        Flip(lights[i],j);//改开关位置的灯
                        if(j<5)
                            Flip(lights[i],j+1);//改右灯
                    }
                }
                if(i<4)
                    lights[i+1] ^= switchs;//改下一行的灯! 用异或!!
                switchs=lights[i]; //第i+1行开关方案和第i行灯情况相同
            }
            if(lights[4]==0){
                outputResult(t,result);
                break;
            }
        }
    }
    return 0;
}

习题解答

习题链接:http://cxsjsx.openjudge.cn/hw202012/

A:画家问题

#include<iostream>
#include<cstring>
using namespace std;
int wall[17][17];
int spread[17][17];
int n;int _count=0;int mmin=999999;

bool trytry() {
	for(int i=1;i<n;i++){
		for(int j=1;j<=n;j++){
			spread[i+1][j]=(wall[i][j]+spread[i][j]+spread[i][j-1]+spread[i][j+1]+spread[i-1][j]+1)%2;
		}
	}
	for(int j=1;j<=n;++j){
		if((spread[n][j-1]+spread[n][j+1]+spread[n-1][j]+spread[n][j])%2==wall[n][j])
			return false;
	}
	return true;
}
int main(){
	cin>>n;
	memset(wall,0,sizeof(wall));
	memset(spread,0,sizeof(spread));
	for(int i=1;i<=n;++i){
		for(int j=1;j<=n;++j){
			char c;
			cin>>c;
			if(c=='w') wall[i][j]=0;//白 0
			else wall[i][j]=1;//黄 1
		}
	}
	int flag=0;
	while(1){
		if(trytry()){
			for(int i=1;i<=n;i++){
				for(int j=1;j<=n;j++){
					if(spread[i][j]) 
					_count++;
				}
			}
			mmin=min(mmin,_count);
			_count=0;
			flag=1;
		}
		for(int i=2;i<=n;i++)
			for(int j=1;j<=n;j++)
				spread[i][j]=0;
		spread[1][1]++;//对第一行进行枚举
		int tmp=1;
		while(spread[1][tmp]>1){//采用反向二进制数每次加一
			spread[1][tmp]=0;tmp++;
			spread[1][tmp]++;
		}
		if(tmp>n) break;
	}
	if(flag==1)
		cout<<mmin<<endl;
	else
		cout<<"inf"<<endl;
	return 0;
}

B:拨钟问题

#include<iostream>
#include<cstring>
using namespace std;
int _clock[10];int a[10][10];int use[10];int out[40];
int cplus[10];int steps;int mmin=9999;

bool trytry(){
	for(int i=4;i<=9;i++){
		if(i>=4&&i<=7)use[i]=(4-(_clock[i-3]+cplus[i-3])%4)%4;
		else if(i==8)use[i]=(4-(_clock[7]+cplus[7])%4)%4;
		else if(i==9)use[i]=(4-(_clock[5]+cplus[5])%4)%4;
		for(int j=1;j<=9;j++){
			cplus[j] += use[i]*a[i][j];
		}
	}
	for(int i=1;i<=9;i++){
		if((cplus[i]+_clock[i])%4!=0) return false;
	}
	steps=0;
	for(int i=1;i<=9;i++) steps+=use[i];
	return true;
}
int main(){
	for(int i=1;i<=9;i++){
		int t;cin>>t;
		_clock[i]=t;
	}
	memset(a,0,sizeof(a));
	memset(out,0,sizeof(out));
	memset(cplus,0,sizeof(cplus));
	for(int i=1;i<=9;i++){
		switch (i)
		{
		case 1:
			a[i][1]=1;a[i][2]=1;a[i][4]=1;a[i][5]=1;break;
		case 2:
			a[i][1]=1;a[i][2]=1;a[i][3]=1;break;
		case 3:
			a[i][2]=1;a[i][3]=1;a[i][5]=1;a[i][6]=1;break;
		case 4:
			a[i][1]=1;a[i][4]=1;a[i][7]=1;break;
		case 5:
			a[i][2]=1;a[i][4]=1;a[i][5]=1;a[i][6]=1;a[i][8]=1;break;
		case 6:
			a[i][3]=1;a[i][6]=1;a[i][9]=1;break;
		case 7:
			a[i][4]=1;a[i][5]=1;a[i][7]=1;a[i][8]=1;break;
		case 8:
			a[i][7]=1;a[i][8]=1;a[i][9]=1;break;
		case 9:
			a[i][5]=1;a[i][6]=1;a[i][8]=1;a[i][9]=1;break;
		}
	}
	for(int i=0;i<=3;i++)
		for(int j=0;j<=3;j++)
			for(int k=0;k<=3;k++){
				use[1]=i;use[2]=j;use[3]=k;
				for(int m=1;m<=3;m++){
					for(int n=1;n<=9;n++){
						cplus[n]+=use[m]*a[m][n];
					}
				}
				if(trytry()&&steps<mmin){
					mmin=steps;
					int tmp=0;
					for(int i=1;i<=9;i++){
						for(int j=1;j<=use[i];j++)
							out[++tmp]=i;
					}
					break;
				}
				for(int m=1;m<=9;m++){
				use[m]=0;cplus[m]=0;
				}
			}
	cout<<out[1];
	for(int i=2;out[i]!=0;i++) cout<<" "<<out[i];
	cout<<endl;
	return 0;
}

C:特殊密码锁

#include<iostream>
#include<cstring>
using namespace std;
int main(){
	int a[32];int b[32];int press[32];int now[32];int step;int mmin=999;int flag=0;
	string s1,s2;
	cin>>s1>>s2;
	for(int i=1;i<=s1.length();i++){
		a[i]=s1[i-1]-'0';now[i]=a[i];
		b[i]=s2[i-1]-'0';
	}
	for(int t=0;t<=1;t++){
		memset(press,0,sizeof(press));
		press[1]=t;
		step=t;
		now[1]^=t;now[2]^=t;
		for(int i=2;i<=s1.length();i++){
			if(now[i-1]!=b[i-1]){
				press[i]=1;step++;
			}
			now[i]^=press[i];
			now[i-1]^=press[i];
			now[i+1]^=press[i];
			if(i==s1.length()&&now[i]==b[i]){
				mmin=min(mmin,step);
				flag=1;
			}
		}
		for(int i=1;i<=s1.length();i++) now[i]=a[i];
	}
	if(flag) cout<<mmin<<endl;
	else cout<<"impossible"<<endl;
	return 0;
}
posted @ 2020-04-07 12:43  Nikaido  阅读(239)  评论(0)    收藏  举报