今天讲的是广度优先搜索,作为大二的稍微有一点基础。上午在练资料书上的枚举,下午练vjudge上的bfs,然后就一直在和代码死磕。
生理周期
OpenJ_Bailian 2997 POJ 1006
人生来就有三个生理周期,分别为体力、感情和智力周期,它们的周期长度为 23 天、28 天和 33 天。每一个周期中有一天是高峰。对于每个人,我们想知道何时三个高峰落在同一天。
对于每个周期,我们会给出从当前年份的第一天开始,到出现高峰的天数(不一定是第一次高峰出现的时间)。你的任务是给定一个从当年第一天开始数的天数,输出从给定时间开始(不包括给定时间)下一次三个高峰落在同一天的时间(距给定时间的天数)。例如:给定
时间为 10,下次出现三个高峰同天的时间是 12,则输出 2(注意这里不是 3)。
输入数据
输入四个整数: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 8910 days.
Case 6: the next triple peak occurs in 10789 days.
OpenJ_Bailian 2997 这个平台上两份代码都显示Output Limit Exceeded 但是在POJ 1006上第二份AC了
在未看解析之前,就直接蛮力穷举法,因为最多可循环21252次,大约是2的15次方,然后肯定会超时
改进之后的代码是先找出第一个能够被第一个周期整除的时间,然后在此时间上累加找出能够被第二个周期整除的时间,之后再每次增加第一个周期和第二个周期的乘积,保证在达到前两个高峰的同时能够被第三个周期整除,进而达到第三个高峰
#include<stdio.h>
int tp = 23;
int te = 28;
int ti = 33;
int main(){
int p,e,i,d,t,no = 1;
while(scanf("%d%d%d%d",&p,&e,&i,&d) && p>=0 && e>=0 && i>=0 && d>=0){
for(t = d+1;t <= 21252;t++){
if(((t-p)%tp == 0)&&((t-e)%te == 0)&&((t-i)%ti == 0)){
printf("Case %d: the next triple peak occurs in %d days.\n",no++,t-d);
break;
}
}
}
return 0;
}
//AC
#include<stdio.h>
int tp = 23;
int te = 28;
int ti = 33;
int main(){
int p,e,i,d,t,no = 1;
while(scanf("%d%d%d%d",&p,&e,&i,&d) && p>=0 && e>=0 && i>=0 && d>=0){
for(t = d+1;t <= 21252;t++){ //Attention 不包括给定时间
if((t-p)%tp == 0){
break;
}
}
for(;t <= 21252;t += tp){
if((t-e)%te == 0){
break;
}
}
for(;t <= 21252;t += tp*te){
if((t-i)%ti == 0){
break;
}
}
printf("Case %d: the next triple peak occurs in %d days.\n",no++,t-d);
}
return 0;
}
称硬币
POJ 1013 OpenJ_Bailian 2692
赛利有 12 枚银币。其中有 11 枚真币和 1 枚假币。假币看起来和真币没有区别,但是重量不同。但赛利不知道假币比真币轻还是重。于是他向朋友借了一架天平。朋友希望赛利称三次就能找出假币并且确定假币是轻是重。例如:如果赛利用天平称两枚硬币,发现天平平
衡,说明两枚都是真的。如果赛利用一枚真币与另一枚银币比较,发现它比真币轻或重,说明它是假币。经过精心安排每次的称量,赛利保证在称三次后确定假币。
输入数据
输入有三行,每行表示一次称量的结果。赛利事先将银币标号为 A-L。每次称量的结果用三个以空格隔开的字符串表示:天平左边放置的硬币 天平右边放置的硬币 平衡状态。其中平衡状态用”up”, “down”, 或 “even”表示, 分别为右端高、右端低和平衡。天平左右的硬币数总是相等的。
输出要求:
输出哪一个标号的银币是假币,并说明它比真币轻还是重。
输入样例
1
ABCD EFGH even
ABCI EFJK up
ABIJ EFGH even
输出样例
K is the counterfeit coin and it is light.
自己最开始的想法也就是先找出天平状态为“even”的一次称量,可以判断此时天平两端都是真币,然后从余下的称量结果中,找出未被证实过(即出现在平衡天平两端的硬币编号中)的硬币即是假币,然后再根据假币所在的左右和天平右端的轻重判断是轻还是重
显然这种想法可行性不高,反正我代码提交的时候显示答案错误。
以下是解析:答案以用两个变量表示:x假币的标号、w假币是比真币轻还是比真币重。x 共有 12 种猜测;w有 2 种猜测。根据赛利设计的称量方案,(x,w )的 24 种猜测中,只有唯一的猜测与三组称量数据都不矛盾。因此,如果猜测(x,w )满足下列条件,这个猜测就是要找的答案:
在称量结果为”even” 的天平两边,没有出现 x ;
如果 w 表示假币比真币轻,则在称量结果为”up” 的天平右边一定出现 x、在称量结果为”down” 的天平左边一定出现 x
如果 w 表示假币比真币重,则在称量结果为”up” 的天平左边一定出现 x、在称量结果为”down” 的天平右边一定出现 x
这里新学习到一个函数:strchr() 函数是用来判断一个字符是否在一个字符串中出现
#include<stdio.h>
#include<string.h>
char coin[13] = {"ABCDEFGHIJKL"};
int flag[12] = {0};
int main(){
int t,i,j;
char right[6],left[6],s[4];
scanf("%d",&t);
while(t--){
for(i = 0;i < 3;i++){
scanf("%s%s%s",right,left,s);
if(s == "even"){ //天平如果平衡则说明此事天平两端都是真币
for(j = 0;j < strlen(right);j++){
flag[right[j]-'A'] = 1;
flag[left[j]-'A'] = 1;
}
}
}
for(i = 0;i < 3;i++){
for(j = 0;j < strlen(right);j++){
if(flag[right[j]-'A'] == 0){
printf("%c is the counterfeit coin and it is %s.\n",right[j],s == "up"?"light":"heavy");
}
if(flag[left[j]-'A'] == 0){
printf("%c is the counterfeit coin and it is %s.\n",left[j],right[j],s == "down"?"light":"heavy");
}
}
}
}
return 0;
}
//Wrong Answer
#include<stdio.h>
#include<string.h>
char right[3][7],left[3][7],s[3][5];
bool isLight(char c){ //判断硬币 x 是否为轻
int i;
for(i = 0;i < 3;i++){
switch(s[i][0]){
case'e':
if(strchr(right[i],c) != NULL || strchr(left[i],c) != NULL){ //strchr() 函数是用来判断一个字符是否在一个字符串中出现
return false;
}
break;
case'u':
if(strchr(right[i],c) == NULL){
return false;
}
break;
case'd':
if(strchr(left[i],c) == NULL){
return false;
}
break;
}
}
return true;
}
bool isHeavy(char c){ //判断硬币 x 是否为重
int i;
for(i = 0;i < 3;i++){
switch(s[i][0]){
case'e':
if(strchr(right[i],c) != NULL || strchr(left[i],c) != NULL){ //strchr() 函数是用来判断一个字符是否在一个字符串中出现
return false;
}
break;
case'd':
if(strchr(right[i],c) == NULL){
return false;
}
break;
case'u':
if(strchr(left[i],c) == NULL){
return false;
}
break;
}
}
return true;
}
int main(){
int t,i;
char c;
scanf("%d",&t);
while(t--){
for(i = 0;i < 3;i++){
scanf("%s %s %s",right[i],left[i],s[i]);
}
for(c = 'A';c <= 'L';c++){
if(isLight(c)){
printf("%c is the counterfeit coin and it is light.\n",c);
break;
}
if(isHeavy(c)){
printf("%c is the counterfeit coin and it is light.\n",c);
break;
}
}
}
return 0;
}
完美立方
a3= b3 + c3 + d3为完美立方等式。例如 123= 63+ 83+ 103。编写一个程序,对任给的正整数 N (N≤100),寻找所有的四元组(a, b, c, d),使得 a3 = b3 + c3 + d3,其中
1<a,b,c,d≤N。
输入数据
正整数 N (N≤100)
输出要求
每行输出一个完美立方,按照 a 的值,从小到大依次输出。当两个完美立方等式中 a 的值相同,则依次按照 b、c、d 进行非降升序排列输出,即 b值小的先输出、然后 c值小的先输出、然后 d 值小的先输出。输入样例
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)
题目比较简单,应该注意的几个问题:1)N的范围是(1,100】,100的3次方为1000000超出了整型数据可以表示的数据范围,所以应该用long long 型储存三次方变量;2)为了避免对一个数多次求立方然后增加时间开销,可以联想到数据预处理的前缀和思想,预先开一个数组存储5到N的立方;3)直接用四重循环求出所有满足条件的四元组;4)考虑到b,c,d均大于1,也就是最小都取2,所以a最小从5开始。
1.0代码运行超时了,因为多次计算了多个数的立方
//Time Limit Exceeded
#include<stdio.h>
#include<math.h>
int main(){
int n;
int a,b,c,d;
scanf("%d",&n);
for(a = 5;a <= n;a++){
for(b = 2;b < a;b++){
for(c = 2;c < a;c++){
for(d = 2;d < a;d++){
if(pow(a,3)-pow(b,3)-pow(c,3)-pow(d,3) == 0 && b<c && b<d && c<d){
printf("Cube = %d, Triple = (%d,%d,%d)\n",a,b,c,d);
break;
}
}
}
}
}
return 0;
}
**2.0代码主要是用了前缀和思想预先把小于N的整数的立方求出来储存在数组中。考虑到需要将数据非降序输出,就增加了b,c,d的大小判断(忽然考虑到满足的条件应该是小于等于。。。不知道代码之前怎么通过了)
//AC 预处理前缀和思想
#include<stdio.h>
#include<math.h>
int main(){
int n,i;
int a,b,c,d;
scanf("%d",&n);
long long r[101];
for(i = 0;i <= n;i++){
r[i] = pow(i,3);
}
for(a = 5;a <= n;a++){
for(b = 2;b < a;b++){ //注意循环范围
for(c = 2;c < a;c++){
for(d = 2;d < a;d++){
if(r[a]-r[b]-r[c]-r[d] == 0 && b<c && b<d && c<d){
printf("Cube = %d, Triple = (%d,%d,%d)\n",a,b,c,d);
break;
}
}
}
}
}
return 0;
}
3.0代码。将b的循环范围缩小至【2,a),将c的循环范围缩小至【b,a),将d的循环范围缩小至【c,a).
//AC
#include<stdio.h>
#include<math.h>
int main(){
int n,i;
int a,b,c,d;
scanf("%d",&n);
long long r[101];
for(i = 0;i <= n;i++){
r[i] = pow(i,3);
}
for(a = 5;a <= n;a++){
for(b = 2;b < a;b++){
for(c = b;c < a;c++){
for(d = c;d < a;d++){
if(r[a]-r[b]-r[c]-r[d] == 0){
printf("Cube = %d, Triple = (%d,%d,%d)\n",a,b,c,d);
break;
}
}
}
}
}
return 0;
}
这三个现在看上去很简单的题,弄了一上午,有很多即是看上去简单的东西可能就仅仅一个点转不过来也会被一直卡在那里
Catch That Cow
Farmer John has been informed of the location of a fugitive cow and wants to catch her immediately. He starts at a point N (0 ≤ N ≤ 100,000) on a number line and the cow is at a point K (0 ≤ K ≤ 100,000) on the same number line. Farmer John has two modes of transportation: walking and teleporting.
- Walking: FJ can move from any point X to the points X - 1 or X + 1 in a single minute
- Teleporting: FJ can move from any point X to the point 2 × X in a single minute.
If the cow, unaware of its pursuit, does not move at all, how long does it take for Farmer John to retrieve it?
Input
Line 1: Two space-separated integers: N and K
Output
Line 1: The least amount of time, in minutes, it takes for Farmer John to catch the fugitive cow.
Sample Input
5 17
Sample Output
4
题目很简单,给你两个x轴上的两个距离x,y,x每一次可进行如下两种变换之一:1)x+1或x-1;2)x*2,求最少经过多少次变换x=y
最开始思路走错了一个方向,认为在
//Error
#include<stdio.h>
int main(){
int n,k;
int t = 0;
//long long n,k;
scanf("%d%d",&n,&k);
while(n != k){
if(n > k){
t += n-k;
break;
}
if(k >= 1.5*n+0.5){
n *= 2;
}
else{
n += 1;
}
t++;
}
printf("%d\n",t);
return 0;
}
2.0代码。用了队列,感觉太久时间没有接触队列了,应该是说数据结构之后就没有写过队列的代码,太生疏了,所以就在百度上看了一些基本用法。数据结构学的是自己写的队列,注重代码底层的实现。但是C++中的queue容器用起来却非常非常方便!!!
直接将每个x的x+1,x-1,x*2都入队列,然后依次出队列,这里如果考虑到x,y的取值范围,数据过大队列的开销太大,就知道应该内存超标了,同时在广度优先搜索的时候在扫描完每一层进入到新的一层时计数加一
//Memory Limit Exceeded
#include<iostream>
#include<queue>
using namespace std; //这几个头文件必不可少
int main(){
int n,k;
int t = 0,r;
//long long n,k;
cin>>n>>k;
if(n > k){
t += n-k;
cout<<t<<endl;
return 0;
}
queue<int> q;
q.push(n);
r = n;
while(n != k){
if(n == r-1){
t++;
r -= 1;
}
n = q.front();
q.pop();
q.push(n-1);
q.push(n+1);
q.push(n*2);
n = q.front();
}
cout<<t<<endl;
return 0;
}
3.0代码。对每一个x的x+1,x-1,x*2都先进行判断,符合相应的条件再入队列,并且队列中的数据是当前的位置和所需要的变换次数,然后依次出队列。同时增加了一个标志数组吗如果该位置已经入过队列,那么第二种情况下到达改点时就不必重复进队列了。
//AC
#include<iostream>
#include<queue>
using namespace std;
const int MAX = 100000;
int flag[MAX] = {0};
struct Point{
int x;
int t;
};
int main(){
int n,k;
//long long n,k;
cin>>n>>k;
if(n >= k){
cout<<n-k<<endl;
return 0;
}
queue<Point> q;
Point p,ans;
p.x = n;
p.t = 0;
q.push(p);
while(!q.empty()){
p = q.front();
q.pop();
if(p.x == k){
break;
}
n = p.x-1;
if(n >= 0 && n <= MAX && !flag[n]){
flag[n] = 1;
ans.t = p.t+1;
ans.x = n;
q.push(ans);
}
n = p.x+1;
if(n >= 0 && n <= MAX && !flag[n]){
flag[n] = 1;
ans.t = p.t+1;
ans.x = n;
q.push(ans);
}
n = p.x*2;
if(n >= 0 && n <= MAX && !flag[n]){
flag[n] = 1;
ans.t = p.t+1;
ans.x = n;
q.push(ans);
}
}
cout<<p.t<<endl;
return 0;
}
迷宫问题
定义一个二维数组:
int maze[5][5] = {
0, 1, 0, 0, 0,
0, 1, 0, 1, 0,
0, 0, 0, 0, 0,
0, 1, 1, 1, 0,
0, 0, 0, 1, 0,
};它表示一个迷宫,其中的1表示墙壁,0表示可以走的路,只能横着走或竖着走,不能斜着走,要求编程序找出从左上角到右下角的最短路线。
Input
一个5 × 5的二维数组,表示一个迷宫。数据保证有唯一解。
Output
左上角到右下角的最短路径,格式如样例所示。
Sample Input
0 1 0 0 0
0 1 0 1 0
0 0 0 0 0
0 1 1 1 0
0 0 0 1 0
Sample Output
(0, 0)
(1, 0)
(2, 0)
(2, 1)
(2, 2)
(2, 3)
(2, 4)
(3, 4)
(4, 4)
这道题是在最后大家都快走完的时候,因为自己实在是不甘心还想再和代码死磕一会,然后终于改好了然后通过的,显示“Accept”的时候真的好感动,因为一个晚上俩个小时用了前一个小时在写代码,让他编译运行通过,然后后面一个小时就都在调试代码让它能在网站上通过了,真的改代码比写代码要心累啊后多,从逻辑上一步一步走了很多遍,硬是觉得没有问题。这道题最开始没有通过的原因是我有一个变量都设为a了,然后再赋值的时候不知不觉就会改变其自身的值,天啦,真的被这一个小毛病坑了好久,所以说还是要有耐心,让自己的代码能够perfect,哪怕一个再小的问题也会成为你AC的障碍
本来考虑用双向搜索的,但考虑到这一道题数据量也不大,就只用了一个队列来广度优先搜索。
#include<iostream>
#include<queue>
using namespace std;
int maze[5][5];
int flag[5][5] = {0};
struct Point{
int x,y;
};
int main(){
int i,j;
queue<Point> q;
Point a,p;
for(i = 0;i < 5;i++){
for(j = 0;j < 5;j++){
cin>>maze[i][j];
}
}
a.x = a.y = 4;
q.push(a);
while((i > 0 || j > 0) && !q.empty()){
a = q.front();
q.pop();
i = a.x+1;
j = a.y;
if(i <= 4 && !maze[i][j] && !flag[i][j]){ //下
flag[i][j] = 1;
p.x = i; //死磕了好久,开始都设为a了
p.y = j;
q.push(p);
}
i = a.x-1;
j = a.y;
if(i >= 0 && !maze[i][j] && !flag[i][j]){ //上
flag[i][j] = 2;
p.x = i;
p.y = j;
q.push(p);
}
i = a.x;
j = a.y+1;
if(j <= 4 && !maze[i][j] && !flag[i][j]){ //右
flag[i][j] = 3;
p.x = i;
p.y = j;
q.push(p);
}
i = a.x;
j = a.y-1;
if(j >= 0 && !maze[i][j] && !flag[i][j]){ //左
flag[i][j] = 4;
p.x = i;
p.y = j;
q.push(p);
}
}
cout<<"(0, 0)"<<endl;
i = j = 0;
while(i != 4 || j != 4){
if(flag[i][j] == 1){
i--;
cout<<"("<<i<<", "<<j<<")"<<endl;
}
if(flag[i][j] == 2){
i++;
cout<<"("<<i<<", "<<j<<")"<<endl;
}
if(flag[i][j] == 3){
j--;
cout<<"("<<i<<", "<<j<<")"<<endl;
}
if(flag[i][j] == 4){
j++;
cout<<"("<<i<<", "<<j<<")"<<endl;
}
}
return 0;
}
小总结:明天继续死磕dfs和bfs.感觉题目做了一定数量之后,会有一个基本的公式套路,然后遇到类似的提就可以直接套进去。送你一句话:永远自在如风!