例题一、完美立方
可以得出一些关键信息,a,b,c,d > 1, b <= c <= d, b < n,
c < n, d < n;
详细C代码如下:
#include<stdio.h>
int main()
{
int a, b, c, d;
int n;
scanf("%d", &n);
for(a = 2; a <= n; a++)
{
for(b = 2; b < n; b++)
{
for(c = b; c < n; c++)
{
for(d = c; d < n; d++)
{
if(a * a * a == b * b * b + c * c * c + d * d *d)
printf("Cube = %3d, Triple = (%3d,%3d,%3d)\n", a, b, c, d);
}
}
}
}
return 0;
}
部分示例运行结果如下:
例题二、生理周期
虽说都是暴力枚举,但是其中还是有技巧的。首先由题意知要输出从给定的日子起,下一次三个高峰同一天的日子的天数,那就是要求下一次三高出现的日期k - d
这里可以直接暴力法
if((k - p) % 23 == 0 && (k - e)%28 == 0 && (k - i)%33 == 0)这种思路是没问题的,但不是最佳的暴力法
我们可以先循环找到某个体力高峰日,然后再用这个值去找情商高峰日,因为是建立在日子处于体力高峰,所以在寻找的过程中要进行k += 23,同时也要判断是否(k - e) % 28 == 0,如果找到了符合条件的,则进入下一个阶段的查找——智商高峰日,这里可以运用一个小技巧——最小公倍数。如果不记得最小公倍数的同学也不必着急,这里我举个例子,大家就会非常清楚这个概念了,同时我想引入最大公约数,一起记
也就是说进入最后一个阶段时,只要考虑(k - i) % 33是否等于零,以lcm(23, 28)来增加k的大小。这里就是核心思路。
详细C代码如下:
#include<stdio.h>
int gcd(int a, int b)
{
if(a % b == 0)
return a;
else
return gcd(b, a % b);
}
#define N 21252
int main()
{
int p, e, i, d;
int cnt = 0;
scanf("%d %d %d %d", &p, &e, &i, &d);
while(p != -1 & e != -1 & i != -1)
{
int k = 0;
int g = 0;
for(k = d + 1; (k - p) % 23 != 0; k++);
for(; (k - e) % 38 != 0; k += 23);
g = 23 * 28 / gcd(23, 28); //最小公倍数
cnt++;
for(; (k - i) % 33 != 0; k += g);
{
if(k <= N)
{
printf("Case %d : The next triple peak occurs in %d days.\n", cnt, k - d);
}
}
scanf("%d %d %d %d", &p, &e, &i, &d);
}
return 0;
}
部分示例结果如下:
例题三、称硬币
这个题可谓是暴力枚举题里的精品了,值得细品
首先我们要设三个二维数组,分别来存取每一次的测试数据以及测量结果。为何我设置列号为5而不是4呢,因为5是这个数组的下限,设为4的话会出现错误,具体为什么以后我会告诉你们。但这不是重点。然后我需要把这些数据存入这三个数组。接下来我需要用一个循环,在字符’A’~'K’之间枚举,找出假币,因为这里不仅仅是找假币,还要查出假币是重是轻。所以需要if(…) else…判断语句,分为两种考虑,重or轻,这里的核心就是设置一个函数,以便代码复用,要不然代码会很冗杂。
这里我先讲一下函数的最后面部分,这样你们就会懂为何我的代码
char *pLeft, *pRight;
if(light)
{
pLeft = Left[i];
pRight = Right[i];
}
else
{
pLeft = Right[i];
pRight = Left[i];
}
要这么写。
这个是个switch语句,是不是感觉之前那些代码没用到result数组,到这里就是它派上用场的时候了!
这里我画图解释一下
这里还有一块知识点值得注意:
strchr(str, c)函数
为了降低明白点我结合官网文件讲,C++官网链接各位兄弟可以收藏一下,以后方便查询,或者收藏我的文章hhhh
strchr()函数是在C语言<string.h>头文件或者C++语言cstring中
用法如下:
const char * strchr ( const char * str, int character );
char * strchr ( char * str, int character );
他可以找到字符串str中第一个出现的字符character,注意str是指针,character是字符常量或变量,这可以查某个字符是否在str所指向的字符串中,如果在,返回的是指针,若不在,则返回NULL
这里有一个关于该函数的example,大家可以练一下手熟悉一下这个函数
#include <stdio.h>
#include <string.h>
int main ()
{
char str[] = "This is a sample string";
char * pch;
printf ("Looking for the 's' character in \"%s\"...\n",str);
pch=strchr(str,'s');
while (pch!=NULL)
{
printf ("found at %d\n",pch-str+1);
pch=strchr(pch+1,'s');
}
return 0;
}
运行结果如下:
Looking for the 's' character in "This is a sample string"...
found at 4
found at 7
found at 11
found at 18
好,这里函数我介绍完了
下面是这题的详细C代码 :
#include<stdio.h>
#include<string.h>
char Left[3][5];
char Right[3][5];
char result[3][5];
int IsFake(char c, int light) //返回值为1表示假币轻,返回值为0表示假币重
{
int i;
for(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 0;
break;
case 'e':
if(strchr(pLeft, c) || strchr(pRight, c))
return 0;
break;
case 'd':
if(strchr(pLeft, c) == NULL)
return 0;
break;
}
}
return 1;
}
int main()
{
int t;
scanf("%d", &t);
getchar();
while(t--)
{
int i;
char n;
for( i = 0; i < 3; ++i)
{
scanf("%s", Left[i]);
scanf("%s", Right[i]);
scanf("%s", result[i]);
}
// for(i = 0; i < 3; ++i)
// {
// printf("%s\n", Left[i]);
// }
// for(i = 0; i < 3; ++i)
// {
// printf("%s\n", Right[i]);
// }
// for(i = 0; i < 3; ++i)
// {
// printf("%s\n", result[i]);
// }
char c;
for(c = 'A'; c <= 'L'; c++)
{
if(IsFake(c, 1))
{
printf("%c is the counterfeit coin and it is light.\n", c);
break;
}
else if(IsFake(c, 0))
{
printf("%c is the counterfeit coin and it is heavy.\n");
break;
}
}
}
return 0;
}
运行结果如下:
例题四、种草问题
LiHua在自家门前方块的空地上中草,已知该空地为n x m的面积,空地上被平均分为了n x m个小块,其中.表示无草,g表示有草。已知每过一个月,长草的那一小块区域会往它的前后左右四个方向蔓延扩张一块区域,请问过了k个月之后,LiHua家前的空地上的具体情况如何?
输入格式:
第一行:输入n和m的值,n和m的值用空格隔开
第N(1 < N <= n)行:输入每行对应的情况,输入m个数据
最后一行输入k
样例1:
这个题还是蛮简单的,一看题目就可以出思路了。首先要一个二维数组a[n][m],然后给a数组个元素赋值。因为要看k个月之后的变化情况,所以需要一个循环,而改变数组的代码我选择定义一个函数来实现,这样可以使代码更加简洁,而且复用性强。这里我需要两个数组,一个用来记录初始的二维数组,另一个用来继承过了一个月改变后的二维数组b[n][m],相当于一个临时数组。然后再将b数组中的值在两层循环下传递给a数组。因为数组从某种意义上来说就是指针,所以调用函数可以进行值传递。
详细C代码如下:
#include<stdio.h>
int n, m;
void f(char a[][m], char b[][m])
{
int i, j;
for(i = 0; i < n; i++)
{
for(j = 0; j < m; j++)
{
if(a[i][j] == 'g')
{
if(j != 0)
b[i][j - 1] = 'g';
if(j != m - 1)
b[i][j + 1] = 'g';
if(i != 0)
b[i - 1][j] = 'g';
if(i != n - 1)
b[i + 1][j] = 'g';
}
}
}
for(i = 0; i < n; i++)
{
for(j = 0; j < m; j++)
{
a[i][j] = b[i][j];
}
}
}
int main()
{
scanf("%d %d", &n, &m);
char a[n][m];
char b[n][m];
int i, j;
getchar();
for(i = 0; i < n; i++)
{
for(j = 0; j < m; j++)
{
scanf("%c",&a[i][j]);
b[i][j] = a[i][j];
}
getchar();
}
int k;
scanf("%d", &k);
getchar();
int t;
for(t = 0; t < k; t++)
{
f(a,b);
}
for(i = 0; i < n; i++)
{
for(j = 0; j < m; j++)
printf("%c", a[i][j]);
printf("\n");
}
return 0;
}
部分示例运行结果如下:
变式4-1、熄灯问题★★★★★
直接思路:枚举所有可能的按钮(开关)状态,对每个状态计算一下最后灯的情况,看是否都熄灭。
一每个按钮有两种状态(按下或不按下)
一共有30个开关,那么状态数是230,太多,会超时
那么我们该如何减少枚举的状态数目呢?
基本思路:如果存在某个局部,一旦这个局部的状态被确定,那么剩个其他部分的状态只能是确定的一种,或者不多的n种,那么就只需枚举这个局部的状态即可。
经观察,第一行就是一个“局部”,当第一行各开关状态确定以后,则熄灭第一行某些亮着的灯,唯一办法就是按下第二行第i列的开关,而要熄灭第二行的灯就需要按下第三行对应的第i行的灯,依次类推,直到最后一行,如果到最后一行灯都是灭的,那么方案成功,即输出结果
既然我们要对第一行入手,那么第一行一共有26种按开关情况。一开始很多人都是想用6层循环,但这里不需要,根据题意,由于每一行是6个元素,那么我们可以直接用一维字符数组装这个5 x 6的数据集。这比用二维数组省事方便些,而且我们直接用一层循环就行,不需要用六层循环。这道题我是用位运算,但是对于位运算可能很多人不是很熟练,这里我会详细讲解!
大致思路如下图所示:
首先我们要按题目要求输入数据,那么就用两层循环进行输入。我定义一个char型的oriLights[5]数组用来存放每个案例,如何存入二进制数呢,我们可以定义一个
SetBit(char *c, int i, int v)函数,c是指向数组的指针,i表示第i位元素,v表示所需设置的数字,如果v = 0,就把第i位元素转化为0,若v = 1,则把第i位元素转化为0。
这里补充一下关于位运算的知识
1)value << shift
左移运算符<<
其中value表示要被操作的整数值,shift是要移动的位数
如果想用左移运算符修改原值,则可以使用<<=
2)value >> shift
右移运算符>>
其中value表示要被操作的整数值,shift是要移动的位数
如果想用右移运算符修改原值,则可以使用>>=
3)位非(求反)运算符(~)
~运算符将每一位转换成它的反面(1转换为0,0转换为1)
例如:3
00000011 ——> 11111100
如果想用~运算符修改原值,则可以使用~=
4)或运算符OR(|)
opt1| opt2
如果两个被操作的值对应位至少有一个为1,则新值相应位为1,否则为0
如果想用|运算符修改原值,则可以使用|=
简单来记就是:
有1为1
全0为0
5)异或运算符XOR(^)
两个数的对应位数的值有一个为1,则新值为1,如果对应的位都为0或1,则新值中相应位为0
如果想用^运算符修改原值,则可以使用^=
b1 | 0 | 1 | 0 | 1 |
---|---|---|---|---|
b2 | 0 | 1 | 1 | 0 |
位新值 | 0 | 0 | 1 | 1 |
6)按位与运算符(&)
opt1 & opt2
规律:
全1为1
有0为0
如果想用&运算符修改原值,则可以使用&=
回到题目,当我们要存入数据时,采用位运算,用预先定义好的SetBit函数将第i行的第j位元素存入原始数据,如果要输入的是0,则字符c |= (1 << i);这里(1 << i)
是把1移到第i位,不能直接让c = (1 << i),要不然达不到改变c的第i位上数字的功能,这里必须用位运算中的|
如果第j位元素为0,则c &= ~(1 << i)
接下来输完数据就是进行枚举了,从0~63,memcpy()函数可以把字符数组内容拷贝给另一个字符数组,用lights数组来存每次枚修改的结果
然后在每一行枚举,把每次枚举的选项传给result[i]数组,然后再进行列循环,如果得到的第j个字符是1,再考虑j是否大于0或者小于5,首先它肯定要反转,因为要让它熄灯,如果大于0,那么它的前一个数要进行反转,小于5的话就让它后一个反转,等结束“列”循环,还要考虑i 是否小于4,因为如果小于4,那么llights[i + 1]也要反转,最后把得到的lights[i]传给switchs,以此往复。直到lights[4]== 0,那么说明枚举出来的方案是正确的
详细C代码如下:
#include<stdio.h>
#include<string.h>
char oriLights[5];
char lights[5];
char result[5];
int GetBit(char c, int i) //得到c的第i位字符
{
return (c >> i) & 1;
}
void SetBit(char *c, int i, int v) //把c的第i位设置成v(0或1)
{
if(v)
{
*c |= (1 << i); //把c的第i位设置成1
}
else
{
*c &= ~(1 << i); //把c的第i位设置成0
}
}
void FlipBit(char *c, int i) //把字符变量c的第i位进行反转
{
*c ^= (1 << i);
}
void OutputResult(int t, char result[])
{
printf("PUZZLE #%d\n", t);
int i;
for(i = 0; i < 5; i++)
{
int j;
for(j = 0; j < 6; j++)
{
printf("%d ", GetBit(result[i], j));
}
printf("\n");
}
}
int main()
{
int T;
scanf("%d", &T); //案例数
int t;
for(t = 1; t <= T; t++)
{
int i, j;
for(i = 0; i < 5; i++)
{
for(j = 0; j < 6; j++)
{
int s;
scanf("%d", &s);
SetBit(&oriLights[i], j, s);
}
}
int n;
for(n = 0; n < 64; n++) //枚举第一行所有可能开关状态
{
int switchs = n;
memcpy(lights, oriLights, sizeof(oriLights));
int i; //拷贝一份oriLights数组数据给lights数组,保证原始数据不受影响
for(i = 0; i < 5; i++)
{
result[i] = switchs;
int j;
for(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;
}
部分示例运行结果如下:
这个解法很特殊也很少见,这是用二进制数进行枚举的方法同时运用了位运算,这个解法很值得细品!!!