枚举:基于逐个尝试答案的一种求解策略
例题1:完美立方
描述:
形如(a^3) = (b^3) +(c^3) +(d^3) 等式称为完美立方等式。例如12,6,8,10。编写一个程序,对任给的正整数N(N<=100),寻找所有的四元组(a,b,c,d),使得满足(a^3) = (b^3) +(c^3) +(d^3) ,其中a,b,c,d大于1,小于等于N,且b<=c<=d。
输入:
一个正整数N(N<=100)
输出:
每行输出一个完美立方。
输入样例:
24
输出样例:
Cube = 6, Triple = (3,4,5)
Cube = 12, Triple = (6,8,10)
Cube = 18, Triple = (2,12,16)
Cube = 18, Triple = (9,12,15)
Cube = 19, Triple = (3,10,18)
Cube = 20, Triple = (7,14,17)
Cube = 24, Triple = (12,16,20)
解题思路:
四重循环枚举a,b,c,d,a在最外层,d在最里层,每一层都是从小到大枚举,范围:a[2,N],b[2,a-1],c[b,a-1],d[c,a-1]
代码:
#include <iostream>
#include <cstdio>//通过使用c标准输入和输出库,使得输入和输出操作也可以在C++实现
using namespace std;
int main()
{
int N,a,b,c,d;
cin >> N;
for(a = 2; a <= N; a++)
for(b = 2; b <= a - 1; b++)
for(c = 2; c <= a - 1; c++)
for(d = 2; d <= a - 1; d++)
{
if(N * N * N == a * a * a + b * b * b + c * c * c + d * d * d)
cout << "Cube = "<< N << "Triple = ( "<< a << "," << b << "," << d << ")" << endl;
}
return 0;
}
总结:
- 申清题意
题目让输入一个正整数,没有让自己判断 - 注意输出内容的处理
cout<<“Cube= “<< N << “Triple = ( " << a <<”,”<< b <<”,"<< d << “)” << endl
注意空格,变量等
例题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。
输出:
从给定日子起,下一次三个高峰同一天的日子。
输入样例:
0 0 0 0
0 0 0 100
5 20 34 325
4 5 6 7
283 102 23 320
203 301 203 40
-1 -1 -1 -1
输出样例:
Case 1:the next triple peak occurs in 21252 days.
Case 2:the next triple peak occurs in 21152 days.
Case 3:the next triple peak occurs in 19575 days.
Case 4:the next triple peak occurs in 16994 days.
Case 5:the next triple peak occurs in 8190 days.
Case 6:the next triple peak occurs in 10789 days.
解题思路:
3. 从d+1天开始,一直试到第21252天,对其中每个日期D,看是否满足 (D-p)%23= =0 && (D-e)%28= =0 && (D-i)%33= =0
4. 跳着试
代码:
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int p,e,i,d, caseNo = 0;
while(cin >> p >> e >> i >> d && p != -1) {
int D;
++caseNo;
for(D = d;(D - p) % 23; D++);
for(;(D - e) % 28;D =+ 23 * 28);
for(;(D - i) % 33;D =+ 23 * 28 * 33);
cout << "Case " << caseNo << ": the next triple peak occurs in" << D-d << "days.";
}
return 0;
}
总结:
- 巧用for循环语句
1)for(D=d;(D-p)%23;D++); 由于值唯一,语句可直接得出答案进行下面的循环
2)for循环中间语句为判断语句,可以写为(D-p)%23,为真继续循环 - 整合语句,使程序简单易读
1)for循环使用
2)while ( cin >> p >> e >> i >> d && p != -1)
例题3:称硬币
描述:
有12枚硬币。其中有11枚真币和1枚假币。假币和真币重量不同,但不知道真币重还是假币重。现在,用一架天平称了这些币三次,告诉你称的结果,请你找出假币并确定假币的重量是轻是重。
输入样例:
1
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][10];
char Right[3][10];
char result[3][10];
bool IsFake(char c, bool 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 a counterfeit coin and it is heavy.\n";
break;
}
}
}
return 0;
}
bool IsFake(char c, bool light)
{
for(int i = 0; i < 3; i++) {
char *pRight, *pLeft;
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 'd':
if(strchr(pLeft,c) == NULL)
return false;
break;
case 'e':
if(strchr(pLeft,c) || strchr(pRight,c))
return false;
break;
}
}
return true;
}
总结:
- 注意循环的包含问题
for(int i=0; i<3;i++)语句仅负责打印数组,不包含剩下语句 - 不要有逻辑性错误
case ‘u’,‘e’,'d’的内容 - 审题不清
1)由于只有一枚假币,所以主函数中判断IsFake语句只要一次为真即可break
2)在IsFake函数中,for(int i = 0; i < 3; i++) 负责判断排查三种情况中的硬币,又因为只有一枚假币,全部检查一遍才能获得答案,所以return true 语句的位置应该在for(int i = 0; i < 3; i++) 循环外面。 - 语法问题
1)case ''语句无需大括号
2)strchr(char *str,char character)
参数说明:str为一个字符串的指针,character为一个待查找字符。
所在库名:#include <string.h>
函数功能:从字符串str中寻找字符character第一次出现的位置。
返回说明:返回指向第一次出现字符character位置的指针,如果没找到则返回NULL。 - 语句整合
while(t–)
例题4:熄灯问题
描述:
— 有一个由按钮组成的矩阵,其中每行有6个按钮,共5行
— 每个按钮的位置上有一盏灯
— 当按下一个按钮后,该按钮以及周围位置(上边,下边,左边,
右边)的灯都会改变状态
— 如果灯原来是点亮的,就会被熄灭
— 如果灯原来是熄灭的,则会被点亮
· 在矩阵角上的按钮改变3盏灯的状态
· 在矩阵边上的按钮改变4盏灯的状态
· 其他的按钮改变5盏灯的状态
— 与一盏灯毗邻的多个按钮被按下时,一个操作会抵消另一次操作
的结果
— 给定矩阵中每盏灯的初始状态,求一种按按钮方案,使得所有灯都熄灭
输入:
— 第一行是一个正整数N,表示需要解决的案例数
— 每个案例由5行组成,每一行包括6个数字
— 这些数字以空格隔开,可以是0或1
— 0表示灯的初始状态是熄灭的
— 1表示灯的初始状态是点亮的
输出:
— 对每个案例,首先输出一行
· 输出字符串“PUZZLE #m”,其中m是该案例的序号
— 接着按照该案例的输入格式输出5行
· 1表示需要把对应的按钮按下
· 0表示不需要按对应按钮
· 每个数字以一个空格隔开
输入样例:
2
0 1 1 0 1 0
1 0 0 1 1 1
0 0 1 0 0 1
1 0 0 1 0 1
0 1 1 1 0 0
0 0 1 0 1 0
1 0 1 0 1 1
0 0 1 0 1 1
1 0 1 1 0 0
0 1 0 1 0 0
输出样例:
PUZZLE#1
1 0 1 0 0 1
1 1 0 1 0 1
0 0 1 0 1 1
1 0 0 1 0 0
0 1 0 0 0 0
PUZZLE#2
1 0 0 1 1 1
1 1 0 0 0 0
0 0 0 1 0 0
1 1 0 1 0 1
1 0 1 1 0 1
解题思路:
想法一:
枚举所有可能的按钮(开关)状态,对每个状态计算一下最后灯的情况,看是否会熄灭。
—— 每个按钮有两种状态(按下或不按下)
—— 一共有30个开关,那么状态数是2^30,太多,会超时
想法二:
存在局部,一旦局部的状态被确定,那么剩余其他部分的状态就只能是确定的一种。
代码:
#include <iostream>
#include <string>
#include <cstring>
#include <memory>
using namespace std;
char oriLights[7];
char Lights[7];
char result[7];
int GetBit(char c, int i)
{
return c& (1 << i);
}
void SetBit(char &c, int i, int v)
{
if(v)
c |= (1 << i);
else
c &= ~ (1 << i);
}
void FlipBit(char &c, int i)
{
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()
{
int T;
cin >> T;
for(int t = 0; 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));
for(int i = 0; i < 5; i++) {
result[i]=switchs;
for(int j = 0; j < 6; j++) {
if(GetBit(switchs, j)) {
if(j > 0)
FlipBit(Lights[i], j-1);
FlipBit(Lights[i], j);
if(j < 5)
FlipBit(Lights[i], j+1);
}
}
if(i < 4)
Lights[i+1] ^= switchs;
switchs = Lights[i];
}
if(Lights[4] == 0) {
OutputResult(t, result);
break;
}
}
}
return 0;
}
总结:
- 数列问题
1)OutputResult函数中对于换行,空格的判断语句
2)main函数中使用FlipBit函数对于bit所处位置的判断 - 题目理解
1)上一行的状态决定下一行的开关灯选择,下一行的开关灯选择改变上一行的状态。
2)for循环的终止条件:最后一行全部关灯 - 位运算
1)^(异或): 和1异或1变0,0变1
2)&(与):有0为0
3)|(或):有1为1
4)~(取反):1变0,0变1
课后习题1:特殊密码锁
描述:
有一种特殊的二进制密码锁,由n个相连的按钮组成(n<30),按钮有凹/凸两种状态,用手按按钮会改变其状态。然而让人头疼的是,当你按一个按钮时,跟它相邻的两个按钮状态也会反转。当然,如果你按的是最左或者最右边的按钮,该按钮只会影响到跟它相邻的一个按钮。当前密码锁状态已知,需要解决的问题是,你至少需要按多少次按钮,才能将密码锁转变为所期望的目标状态。
输入:
两行,给出两个由0、1组成的等长字符串,表示当前/目标密码锁状态,其中0代表凹,1代表凸。
输出:
至少需要进行的按按钮操作次数,如果无法实现转变,则输出impossible。
输入样例:
011
000
输出样例:
1
解题思路:
1.n<30,全部枚举空间约为536,870,912种,无法在规定短时间内验证完毕。
2.除了第一个和最后一个按钮,按下中间的任何一个按钮,均会改变相邻两个按钮状态,因此如果某一位置按钮与目标密码锁状态不同,前一位置状态已经相同,则均不得按下此位置按钮,可以通过按下此位置的后一位置按钮来达到目的。
3.第一个按钮是否按下,直接决定了整个密码锁后续按钮是否需要按下。枚举空间为2,而不是2^29。
代码:
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
int oriLock;
int lock;
int destLock;
inline int GetBit(int n,int i)
{
return (n >> i) & 1;
}
inline void SetBit(int & n,int i,int v)
{
if(v)
n |= (1 << i);
else
n &= ~(1 << i);
}
inline void FlipBit(int & n,int i)
{
n ^= (1 << i);
}
int main()
{
char line[40];
destLock = lock = oriLock = 0;
cin >> line;
int N = strlen(line);//头文件<cstring>的函数
for(int i = 0; i < N; ++i)
SetBit(oriLock,i, line[i] - '0');//和GetBit用途无区别,多设置一个变量line方便计算密码锁个数
cin >> line;
for(int i = 0; line[i]; ++i)
SetBit(destLock,i, line[i] - '0');
int minTimes = 1 << 30;//注意放在循环外
for(int p = 0; p < 2; ++p) {
lock = oriLock;
int times = 0;//放在该循环里可以验证两次最小时间
int curButton = p;
for(int i = 0; i < N; ++i) {
if(curButton) {
++ times;
if( i > 0)
FlipBit(lock,i-1);
FlipBit(lock,i);
if( i < N-1)
FlipBit(lock,i+1);
}
if( GetBit(lock,i) != GetBit(destLock,i))
curButton = 1;
else
curButton = 0;
}
if( lock == destLock)//选用int型变量的优势
minTimes = min(minTimes ,times);//头文件<algorithm>的函数;记录minTimes的值而不是break
}
if( minTimes == 1 << 30)
cout <<"impossible" << endl;
else
cout << minTimes << endl;
return 0;
}
总结:
- 语法问题
1)已知 char line = 1,则 int v= line-‘1’= 0; int t= line-1 = 48
2)strlen()计算字符串长度,包含在cstring头文件里
3)左移乘以2,右移除以2
4)inline函数:为了解决一些频繁调用的小函数大量消耗栈空间的问题。但是只适合函数体内部代码简单的函数使用,不能包含复杂的结构控制语句例如while、switch,并且内联函数本身不能是直接递归函数。
5)在对数组名称使用 == 运算符时,运算符会比较数组的开始内存地址,而不是数组的内容。要比较两个数组的内容,则必须比较它们各自的元素。 - 逻辑问题
1)注意times,minTimes变量定义位置。
2)minTimes=1<<30原因是每个密码变换,两种方式,共计2*30=60个单位时间。
课后习题2:拨钟问题
描述:
有9个时钟,排成一个3*3的矩阵。
|-------| |-------| |-------|
| | | | | | |
|---O | |---O | | O |
| | | | | |
|-------| |-------| |-------|
A B C
|-------| |-------| |-------|
| | | | | |
| O | | O | | O |
| | | | | | | | |
|-------| |-------| |-------|
D E F
|-------| |-------| |-------|
| | | | | |
| O | | O---| | O |
| | | | | | | |
|-------| |-------| |-------|
G H I
(图 1)
现在需要用最少的移动,将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
解题思路:
暴力枚举
代码:
//解法一:递归
#include <iostream>
#include <bitset>
#include <algorithm>
#include <functional>
#include <cstring>
using namespace std;
int oriClocks[9];
int clocks[9];
const char * moves[9] = { "ABDE","ABC","BCEF","ADG","BDEFH","CFI","DEGH","GHI","EFHI" };
int moveTimes[9] = {0};
int result[9];
int minTimes = 4*9;
void Enum(int n)
{
if( n >= 9 ) {
memcpy(clocks,oriClocks,sizeof(clocks));
int totalTimes = 0;
for( int i = 0;i < 9 ; ++ i ) {
if( moveTimes[i] ) {
for( int k = 0; moves[i][k]; ++k) {
clocks[moves[i][k]-'A'] = (clocks[moves[i][k]-'A'] + moveTimes[i]) % 4;//计算余数
totalTimes += moveTimes[i];
}
}
}
int i;
for( i = 0;i < 9; ++i )
if( clocks[i])
break;//存在时钟不是12点,移动失败
if( i == 9) {
if( minTimes > totalTimes) {
minTimes = totalTimes;
memcpy(result,moveTimes,sizeof(result));
}
}
return ;
}
for( int i = 0;i < 4; ++ i ) {
moveTimes[n] = i;
Enum(n+1);
}
return ;
}
int main()
{
for( int i = 0;i < 9 ; ++i )
cin >> oriClocks[i];
Enum(0);
for( int i = 0; i < 9; ++i )//遍历每一种移动方式
for( int k = 0; k < result[i] ; ++ k )//遍历移动次数
cout << i+1 << " ";
return 0;
}
//部分递归结果如下:
/*
moveTimes={0,0,0,0,0,0,0,0,0,}
moveTimes={0,0,0,0,0,0,0,0,1,}
moveTimes={0,0,0,0,0,0,0,0,2,}
moveTimes={0,0,0,0,0,0,0,0,3,}
只有8的可能性
moveTimes={0,0,0,0,0,0,0,1,0,}
moveTimes={0,0,0,0,0,0,0,1,1,}
moveTimes={0,0,0,0,0,0,0,1,2,}
moveTimes={0,0,0,0,0,0,0,1,3,}
moveTimes={0,0,0,0,0,0,0,2,0,}
moveTimes={0,0,0,0,0,0,0,2,1,}
moveTimes={0,0,0,0,0,0,0,2,2,}
moveTimes={0,0,0,0,0,0,0,2,3,}
moveTimes={0,0,0,0,0,0,0,3,0,}
moveTimes={0,0,0,0,0,0,0,3,1,}
moveTimes={0,0,0,0,0,0,0,3,2,}
moveTimes={0,0,0,0,0,0,0,3,3,}
7+8的可能性
*/
总结:
- 语法问题
1)bitset创造一个内含位或布尔值且大小固定的数组(array)。当需要管理各种标识,并需要以标识的任意组合表现变量时,即可使用bitset模板类。
2) 注意memcpy用法。 - 逻辑问题
1)递归是从n=8开始,moveTimes[8]=0,1,2,3,进行只有移动方式8的列举; 然后返回上一级,moveTimes[7]=0,Enum(9)再次得到moveTimes[8]=1,进行移动方式7+8的组合列举,以此类推。
来源:CSDN
作者:高无语
链接:https://blog.csdn.net/qq_42617439/article/details/104823310