1048: 宽搜3(巧妙取量)题目在此
时间限制: 1 Sec 内存限制: 128 MB
题目描述
【题目描述】
有三个容器,容量分别为 a,b,c(a> b > c ),一开始a装满油,现在问是否只靠abc三个容器量出k升油。
如果能就输出“yes”,并且说明最少倒几次,否则输出“no”。
例如:10升油在10升的容器中,另有两个7升和3升的空容器,要求用这三个容器倒油,
使得最后在abc三个容器中有一个刚好存有5升油,问最少的倒油次数是多少?
(每次倒油,A容器倒到B容器,或者A内的油倒完,或者B容器倒满。 )
10 7 3
(10 0 0)
(3 7 0):第一次
(3 4 3):第二次
(6 4 0):第三次
(6 1 3):第四次
(9 1 0):第五次
(9 0 1):第六次
(2 7 1):第七次
(2 5 3):第八次,出现5了。
【输入格式】
输入有多组测试数据。
输入a,b,c, k四个正整数( 100 a > b > c > = 1 , 1 < = k < 100 )
【输出格式】
如果能得到k就输出两行
第一行“yes”,第二行为最少的次数,否则输出“no”
【样例输入】
10 7 3 5
【样例输出】
yes
8
思路:首先的话,我们看到了这道题之后,第一感觉一定是搜索对吧,而且因为是要一直判断的,所以我们可以使用宽搜,这个都是可以理解的吧。然后这道题目,其实在一定意义上面就是我们小学做过的一种奥数题的类型,真是扎心,接下来,按照老规矩,要审题,这道题目需要我们输出两个,同时是多组数据,这个是要注意的,然后看样例得到的过程,不难看出,这个就是一个换罐子,把多的移到少的,一直凑啊凑啊的过程,那么这个思路就出来了,宽搜+多次判断,
这个判断有六种情况我在这里列举一下吧
用6个判断,来判断x,y,z两两之间的最小值,进行换罐子
6种情况如下:
1.my-tno.y,tno.x 2.mz-tno.z,tno.x 3.mx-tno.x,tno.y
4.mz-tno.z,tno.y 5.mx-tno.x,tno.z 6.my-tno.y,tno.z
到这里都能够理解的默默举个爪子呗,很好(我是不是有点自作多情了,没关系继续),这道题的数据说真的还真不小,反正我定了1110000,然后就编目录,通俗讲就是定义结构体,而且有一个要注意的就是,因为要判断是否可以输出答案,就要用到一个bool bk来判断,如果等于true,说明有答案可以输出,如果等于false,说明没有答案输出,总体来讲大概就是这样子吧,让我再瞄一眼代码啊,哦哦哦哦哦有个
大大大重点:那就是我们在把原来的数据保存在结构体里面的时候,要把三个数结合起来保存,这样就可以只用一个数组来保存,否则就要用三个,这样就会大大大大大的浪费空间和时间,
什么意思呢?就是比如说: a=55 , b=76 , c=33,那么如果我们用常人的思维,是不是用三个数组来保存再带人结构体里面,这样不说都知道会浪费很多的空间,最骇人的是这样要判断多次,很容易出错。所以我们就要把 a*10000+b*100+c=55*10000+76*100+33=557633,那么这样就是在不影响他本身的情况下,用一个数组来保存,就可以使得代码更加简洁,同时判断起来也更加方便,bool数组的判断也只用一个就okk了。
大致的思路就是这样,剩下的看具体代码的实现吧,代码也写得非常清楚了,不懂的就就就再看几遍吧
/*
这种做法就是一直判断,就是把每一种情况都列出来,找出最早成立的那一个
还有一个就是,因为是一种一种情况来判断的
所以用队列来处理也是可以的
然后我们要用6个判断,来判断x,y,z两两之间的最小值,进行换罐子
6种情况如下:
1.my-tno.y,tno.x 2.mz-tno.z,tno.x 3.mx-tno.x,tno.y
4.mz-tno.z,tno.y 5.mx-tno.x,tno.z 6.my-tno.y,tno.z
*/
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
struct node
{
int x,y,z,s;//x,y,z表示的就是题目中的a,b,c;s表示的是步数
}list[1110000];
bool v[1110000];//判断重复的
int kk,head,tail,mx,my,mz;//head和tail不解释
inline int ys(node tno) {return tno.x*10000+tno.y*100+tno.z;}
/*
这是节省空间的一步,就比如说:22 55 77
那么经过了ys这一步之后就变成了 225577
把三个数拼在一起变成一个数来判断
如果不用这一步的话,就要分三个来保存自然就会浪费空间
*/
inline int min(int x,int y) {return x<y?x:y;}
inline int max(int x,int y) {return x>y?x:y;}
int main()
{
while(scanf("%d%d%d%d",&mx,&my,&mz,&kk)!=EOF)//多组数据
{
list[1].x=mx; list[1].s=list[1].y=list[1].z=0;
/*
这一步就是初始化,就是在最开始把所有的油都放在最大的罐子里
那么其他的东西都为0,因为油都放在最大的罐子mx里
my和mz就是空的,步数也是空的
*/
head=1; tail=2; memset(v,false,sizeof(v));
/*
这个理解成初始化,也就是说,把这个过程变成了一个委屈巴巴的队列,
head=1,tail=2是为了方便,这样就能清楚看出当前的这个队列是有人的
*/
v[ys(list[1])]=true;//第一种情况
bool bk=false;//bk是用来判断输出的
while(head<tail)//这样就表示当前这个队列有人
{
node tno=list[head]; int k;
//定义tno是为了不打那么多,防止出错
//k是用来找最小的来换罐子,按照做法可以这么理解
if(tno.x==kk || tno.y==kk || tno.z==kk)//如果有一个满足题目的要求
{
printf("yes\n%d\n",tno.s);//就输出yes并输出步数(tno.s代表经过的步数)
bk=true; break;//退出
}
k=min(my-tno.y,tno.x);
/*
如果没有满足题目的条件,就进行换罐子,
用最小值来给油换罐子(第二个罐子的总存量-第二个罐子当前的量和第一个罐子的量做比较)
(这一步是寻找要换的罐子)
*/
tno.x-=k; tno.y+=k;//这个表示的就是换罐子的过程
if(!v[ys(tno)])
/*
这一步就是判断重复的过程,ys上面解释过了,
然后在上面的第一次换罐子的时候我们将第一种情况记录下来了,
也就是说,只要当前的情况和成立过的情况不一样就是OK的
*/
{
v[ys(tno)]=true; tno.s++;
/*
进入了循环之后,说明我们找到了一种新的情况,
这个时候就要用v数组把这种情况保存起来,用来后面判断,
然后这个时候,我们是进行了下一轮的换罐子,
所以tno.s++,就是在步数那里增加一种情况
*/
if(tno.x==kk || tno.y==kk || tno.z==kk)
{
printf("yes\n%d\n",tno.s);
bk=true; break;
}//同样的判断,如果成立就输出,不成立就退出,进行下一步的操作
list[tail]=tno; tail++;
/*
这一步就是把当前这一种情况的值保存在list这个结构体里面,
解释一下:list[head]表示的是第一种情况,然后list[tail]就是用来保存每一种情况的状态,
然后我们在进行下一步之前,要把tno的状态变回list[head]的状态,
这样才可以保证不会有情况漏掉,也可以保证每一种情况的6种状态都可以经历一次
*/
}
tno=list[head];//恢复初始状态
k=min(mz-tno.z,tno.x);//寻找要换的罐子
tno.x-=k; tno.z+=k;//换罐子
if(!v[ys(tno)])
{
v[ys(tno)]=true; tno.s++;
if(tno.x==kk || tno.y==kk || tno.z==kk)
{
printf("yes\n%d\n",tno.s);
bk=true; break;
}
list[tail]=tno; tail++;
}
tno=list[head];
k=min(mx-tno.x,tno.y);
tno.y-=k; tno.x+=k;//和之前的一样
if(!v[ys(tno)])
{
v[ys(tno)]=true; tno.s++;
if(tno.x==kk || tno.y==kk || tno.z==kk)
{
printf("yes\n%d\n",tno.s);
bk=true; break;
}
list[tail]=tno; tail++;
}
tno=list[head];
k=min(mz-tno.z,tno.y);
tno.y-=k; tno.z+=k;//和之前的一样
if(!v[ys(tno)])
{
v[ys(tno)]=true; tno.s++;
if(tno.x==kk || tno.y==kk || tno.z==kk)
{
printf("yes\n%d\n",tno.s);
bk=true; break;
}
list[tail]=tno;tail++;
}
tno=list[head];
k=min(mx-tno.x,tno.z);
tno.z-=k; tno.x+=k;//和之前的一样
if(!v[ys(tno)])
{
v[ys(tno)]=true; tno.s++;
if(tno.x==kk || tno.y==kk || tno.z==kk)
{
printf("yes\n%d\n",tno.s);
bk=true; break;
}
list[tail]=tno; tail++;
}
tno=list[head];
k=min(my-tno.y,tno.z);
tno.z-=k; tno.y+=k;//和之前的一样
if(!v[ys(tno)])
{
v[ys(tno)]=true; tno.s++;
if(tno.x==kk || tno.y==kk || tno.z==kk)
{
printf("yes\n%d\n",tno.s);
bk=true; break;
}
list[tail]=tno; tail++;//和之前的一样
}
head++;
/*
但是这个时候我们就已经把head=1的时候的状态的6种情况都跑了一遍,
这个时候就是跑第二种情况要面临的6种状态,接着第三种一直到可以与题目相同为止
*/
}
if(bk==false) printf("no\n");
/*
如果bk在跑完了所以之后,仍然等于false,
说明没有可以成立的状态,那么这么时候就输出no
*/
}
return 0;
}
简直就是大大大大的okk,要是有大佬来临的话我想放一个蒟蒻不懂的代码,望大佬点评。
#include<cstdio>
#include<cstring>
#include<queue>
#include<cmath>
using namespace std;
struct node
{
int a,b,c,step;
}s,e;
int a,b,c,k;
bool f[105][105][105];
int bfs(node x)
{
queue<node> q;
q.push(x);
f[x.a][x.b][x.c]=true;
while(!q.empty())
{
node cur=q.front();
q.pop();
if(cur.a==k||cur.b==k||cur.c==k)
{
return cur.step;
}
if(cur.a>0&&cur.b<b)
{
int t=min(cur.a,b-cur.b);
node temp;
temp.a=cur.a-t;
temp.b=cur.b+t;
temp.c=cur.c;
temp.step=cur.step+1;
if(!f[temp.a][temp.b][temp.c])
{
q.push(temp);
f[temp.a][temp.b][temp.c]=1;
}
}
if(cur.a>0&&cur.c<c){
int t=min(cur.a,c-cur.c);
node temp;
temp.a=cur.a-t;
temp.b=cur.b;
temp.c=cur.c+t;
temp.step=cur.step+1;
if(!f[temp.a][temp.b][temp.c])
{
q.push(temp);
f[temp.a][temp.b][temp.c]=1;
}
}
if(cur.b>0&&cur.c<c)
{
int t=min(cur.b,c-cur.c);
node temp;
temp.a=cur.a;
temp.b=cur.b-t;
temp.c=cur.c+t;
temp.step=cur.step+1;
if(!f[temp.a][temp.b][temp.c])
{
q.push(temp);
f[temp.a][temp.b][temp.c]=1;
}
}
if(cur.b>0&&cur.a<a)
{
int t=min(cur.b,a-cur.a);
node temp;
temp.a=cur.a+t;
temp.b=cur.b-t;
temp.c=cur.c;
temp.step=cur.step+1;
if(!f[temp.a][temp.b][temp.c])
{
q.push(temp);
f[temp.a][temp.b][temp.c]=1;
}
}
if(cur.c>0&&cur.a<a)
{
int t=min(cur.c,a-cur.a);
node temp;
temp.a=cur.a+t;
temp.b=cur.b;
temp.c=cur.c-t;
temp.step=cur.step+1;
if(!f[temp.a][temp.b][temp.c])
{
q.push(temp);
f[temp.a][temp.b][temp.c]=1;
}
}
if(cur.c>0&&cur.b<b)
{
int t=min(cur.c,b-cur.b);
node temp;
temp.a=cur.a;
temp.b=cur.b+t;
temp.c=cur.c-t;
temp.step=cur.step+1;
if(!f[temp.a][temp.b][temp.c])
{
q.push(temp);
f[temp.a][temp.b][temp.c]=1;
}
}
}
return -1;
}
int main()
{
while(scanf("%d%d%d%d",&a,&b,&c,&k)!=EOF)
{
s.a=a,s.b=0,s.c=0,s.step=0;
memset(f,0,sizeof(f));
int ans=bfs(s);
if(ans==-1) printf("no\n");
else
{
printf("yes\n%d\n",ans);
}
}
return 0;
}
我大致也看了一下,发现其实和我原来的代码本质是一样的,也是判断那六种情况:
用6个判断,来判断x,y,z两两之间的最小值,进行换罐子
6种情况如下:
1.my-tno.y,tno.x 2.mz-tno.z,tno.x 3.mx-tno.x,tno.y
4.mz-tno.z,tno.y 5.mx-tno.x,tno.z 6.my-tno.y,tno.z