例题一 “画家问题”
例题二 “拨钟问题”
例题三 “特殊密码锁”
【描述】
有一个正方形的墙,由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
#include
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
#include
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
#include
#include
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;
}