C++程序设计趣题:枚举

目录

例题一 “画家问题”
例题二 “拨钟问题”
例题三 “特殊密码锁”


例题一 “画家问题”

【描述】
有一个正方形的墙,由N*N个正方形的砖组成,其中一些砖是白色的,另外一些砖是黄色的。Bob是个画家,想把全部的砖都涂成黄色。但他的画笔不好使。当他用画笔涂画第(i, j)个位置的砖时, 位置(i-1, j)、 (i+1, j)、 (i, j-1)、 (i, j+1)上的砖都会改变颜色。请你帮助Bob计算出最少需要涂画多少块砖,才能使所有砖的颜色都变成黄色。


【输入】
第一行是一个整数n (1≤n ≤15),表示墙的大小。接下来的n行表示墙的初始状态。每一行包含n个字符。第i行的第j个字符表示位于位置(i,j)上的砖的颜色。“w”表示白砖,“y”表示黄砖。
【输出】
一行,如果Bob能够将所有的砖都涂成黄色,则输出最少需要涂画的砖数,否则输出“inf”。
【样例输入】

5
wwwww
wwwww
wwwww
wwwww
wwwww

【样例输出】

15 

【来源】OJ


【解】

#include<iostream>
#include<string.h>
using namespace std;


int nSide = 0;//一边上砖的个数
int *ori, *proc;//初始状态数组和涂色中数组
int tempCountMin = 100000;//失败则取100000.
void setBit(int &n, int i, int opr);//设置n的第i位为opr(0或1)
int findBit(int n, int i);//查找
void switBit(int &n, int i);//改变
void Paint(int &n, int i, int line);//涂色操作

int main() {
	cin >> nSide; getchar();
	ori = new int[nSide]; proc = new int[nSide];
	memset(&(ori[0]), 0, 4 * nSide); memset(&(proc[0]), 0, 4 * nSide);
	for(int i = 0; i < nSide; ++i) {
		for(int j = 0; j < nSide; ++j) {
			char temp = 0; temp=getchar();
			//从左往右从低位开始
			setBit(ori[i], j, (temp == 'y')?1:0);
		}
		getchar();
	}
	
	
	//对第一行各个砖的操作与否 可以表示成一个0~(2^nSide)-1的整数.
	int OPERATE = 0; 
	int CountMin =100000;
	int stdResult = 0; for(int i = 0; i < nSide; i++) 
	{ stdResult += (1 << i); }
	//枚举
	for(; OPERATE < (1 << nSide); ++OPERATE) {
		tempCountMin = 0;
		memcpy(proc, ori, 4 * nSide);
		//改变第一行
		for(int i=0;i<nSide;++i)
			if((OPERATE >> i) & 1) {
				Paint(proc[0], i, 0);
			}

		for(int line = 1; line < nSide; ++line) {
			for(int i = 0; i < nSide; ++i) {//列
				if(!findBit(proc[line - 1], i)) {
					Paint(proc[line], i, line);
				}
			}
		}
		
		if(proc[nSide - 1] == stdResult) {//涂完了,比较
			if(tempCountMin < CountMin)CountMin = tempCountMin;
		}
	}

	if(CountMin == 100000) { printf("inf"); }
	else printf("%d", CountMin);

	if(ori)delete[]ori; if(proc)delete[]proc;

	int n; cin >> n; return 0;
}

void setBit(int & n, int i, int opr)
{
	if(opr)n|=(1 << i);
	else n &= (~(1 << i));
}

int findBit(int n, int i)
{
	return (n >> i) & 1;
}

void switBit(int & n, int i)
{
	n ^= (1 << i);
	return;
}

void Paint(int &n, int i,int line) {
	++tempCountMin;
	switBit(n, i);
	if(i > 0)switBit(n, i - 1);
	if(i < nSide - 1)switBit(n, i + 1);

	int*pt = &n;
	if(line > 0)switBit(*(pt - 1), i);
	if(line < nSide - 1)switBit(*(pt + 1), i);
	return;
}

【说明】
涂色次序不影响结果;
上一行操作结束后,为保证上一行满足要求,本行的操作确定;
结论:只需枚举对第一行的操作,只需检查操作后最后一行是否满足要求。
【注】
此处用一个Bit记录一个砖的颜色,这样一个整型数即可记录一行;涂色操作采用位运算进行。
快。


例题二 “拨钟问题”

【描述】
有9个时钟,排成一个3*3的矩阵:

现在需要用最少的移动,将9个时钟的指针都拨到12点的位置。共允许有9种不同的移动。如下表所示,每个移动会将若干个时钟的指针沿顺时针方向拨动90度。

移动 影响的时钟

1 ABDE
2 ABC
3 BCEF
4 ADG
5 BDEFH
6 CFI
7 DEGH
8 GHI
9 EFHI
【输入】
9个整数,表示各时钟指针的起始位置,相邻两个整数之间用单个空格隔开。其中,0=12点、1=3点、2=6点、3=9点。
【输出】
输出一个最短的移动序列,使得9个时钟的指针都指向12点。按照移动的序号从小到大输出结果。相邻两个整数之间用单个空格隔开。
【样例输入】

3 3 0 
2 2 2 
2 1 2 

【样例输出】

4 5 8 9 

【来源】OJ
【解】和上题比较类似:

#include<iostream>
#include<string.h>
using namespace std;

int OriC[10] = { 0 };//初始状态
int ProcC[10] = { 0 };//当前状态
int CountStep[10] = { 0 };//记录当前方案操作
int CountMin[10] = { 0 };//最小操作

void switC(int index,int times);//拨钟
void Inc(int index);//增加某一位
void JW();//满4进位
bool is_compl();//判断是否读完

int main() {
	for(int i = 1; i < 10; i++) {
		cin >> OriC[i];
	}
	int minOpr = 36;
	//开始枚举
	const int FirstLine = 63;//第一行的总枚举数
	const int lastline = 63;//最后一行总枚举数

	for(int try1 = 0; try1 <= FirstLine; ++try1) {
		//cout << "[test]trial #1: " << try1 << endl;//*************
		memcpy(ProcC, OriC, 40);
		memset(CountStep, 0, 40);
		//尝试第一排
		switC(1, try1 & 3); switC(2, (try1>>2)&3); switC(3, (try1>>4)&3);
		JW();
		//再试第二排
		switC(4, (4 - ProcC[1])%4);
		switC(6, (4 - ProcC[3])%4);
		switC(5, (4 - ProcC[2])%4);
		
		//第三排
		int middleTempClock[10]; memcpy(middleTempClock, ProcC, 40);
		int middleCount[10]; memcpy(middleCount, CountStep, 40);

		for(int try3 = 0; try3 <= lastline; ++try3) {
			memcpy(ProcC, middleTempClock, 40);
			memcpy(CountStep, middleCount, 40);

			switC(7, try3 & 3); switC(8, (try3 >> 2) & 3); switC(9, (try3 >> 4) & 3);
			JW();
			//for(int i = 0; i < 3; i++) { cout << '\n'; for(int j = 0; j < 3; j++)cout << ProcC[1 + 3 * i + j] << ' '; }
			//********************************************
			if(is_compl()) {
				int tempSteps = 0;
				for(int i = 1; i < 10; i++)tempSteps += CountStep[i];
				if(tempSteps < minOpr) {
					//cout << "[test]succeed. #3: " << try3 << endl;//*********************
					minOpr = tempSteps;
					memcpy(CountMin, CountStep, 40);
				}
			}
		}
	}

	for(int i = 1; i < 10; i++) {
		if(CountMin[i]) { for(int tt = 0; tt < CountMin[i]; tt++) { cout << i << ' '; } }
	}
	cin >> minOpr;
	return 0;
}
void switC(int index,int times)
{
	if(index < 1 || index>9)return;
	while(times--) {
		++CountStep[index];
		switch(index) {
		case 1: {
			Inc(1); Inc(2); Inc(4); Inc(5); break;
		}
		case 2: {
			Inc(2); Inc(1); Inc(3); break;
		}
		case 3: {
			Inc(2); Inc(3); Inc(5); Inc(6); break;
		}
		case 4: {
			Inc(1); Inc(4); Inc(7); break;
		}
		case 5: {
			Inc(2); Inc(4); Inc(5); Inc(6); Inc(8); break;
		}
		case 6: {
			Inc(3); Inc(6); Inc(9); break;
		}
		case 7: {
			Inc(4); Inc(5); Inc(7); Inc(8); break;
		}
		case 8: {
			Inc(7); Inc(8); Inc(9); break;
		}
		case 9: {
			Inc(5); Inc(6); Inc(8); Inc(9); break;
		}
		default:break;
		}
	}
	return;
}
void Inc(int index)
{
	++ProcC[index]; return;
}
void JW()
{
	for(int&i : ProcC) { while(i >= 4)i -= 4; }return;
}
bool is_compl()
{
	for(int i = 1; i < 10; i++)if(ProcC[i] != 0)return false;
	return true;
}

例题三 ”特殊密码锁“

【描述】
有一种特殊的二进制密码锁,由n个相连的按钮组成(n<30),按钮有凹/凸两种状态,用手按按钮会改变其状态。

然而让人头疼的是,当你按一个按钮时,跟它相邻的两个按钮状态也会反转。当然,如果你按的是最左或者最右边的按钮,该按钮只会影响到跟它相邻的一个按钮。

当前密码锁状态已知,需要解决的问题是,你至少需要按多少次按钮,才能将密码锁转变为所期望的目标状态。

【输入】
两行,给出两个由0、1组成的等长字符串,表示当前/目标密码锁状态,其中0代表凹,1代表凸。
【输出】
至少需要进行的按按钮操作次数,如果无法实现转变,则输出impossible
【样例输入】

011
000

【样例输出】

1

【来源】OJ
【解】更简单了,确定对第一个的操作后,其它的的就被确定。
使用一个整型数即可记录状态。

#include<iostream>
#include<string.h>
#include<algorithm>
using namespace std;

int Ori = 0, End = 0;
int Proc;//动态
int countWei = 0;//输入的位数
int MinCount = 100;
int countTemp = 0;
int SwitBit(int index);//改变其值并返回改变后的值
int GetBit(int &n, int index);//返回这个位上的值
int Operation(int index);//对一个位操作
//index第几位

int main(){
	
		char c = 0;
		while(c = getchar()) {
			if(c == '\n')break;
			if(c == '1')Ori += (1 << countWei);
			++countWei;
		}
		for(int i = 0; i < countWei; ++i) {
			c = getchar();
			if(c == '1')End += (1 << i);
		}

		//Input
		countTemp = 0; Proc = Ori;
		//如果按第一个
		Operation(0);
		for(int i = 1; i < countWei; ++i) {
			if(GetBit(End, i - 1) != GetBit(Proc, i - 1))Operation(i);
		}
		if(Proc == End) {
			if(countTemp < MinCount)MinCount = countTemp;
		}

		//如果不按第一个
		countTemp = 0; Proc = Ori;
		for(int i = 1; i < countWei; ++i) {
			if(GetBit(End, i - 1) != GetBit(Proc, i - 1))Operation(i);
		}
		if(Proc == End) { if(countTemp < MinCount)MinCount = countTemp; }//
		if(MinCount == 100)cout << "impossible";
		else cout << MinCount;

		cin >> End;
	
	return 0;
}

int SwitBit(int index)
{
	Proc ^= (1 << index);
	return (Proc>>index)&1;
}

int GetBit(int &n, int index)
{
	return (n>>index)&1;
}

int Operation(int index)
{
	++countTemp;
	SwitBit(index);
	if((index - 1) >= 0)SwitBit(index - 1);
	if(index + 1 < countWei)SwitBit(index + 1);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值