HDU1495非常可乐

题目链接

Problem Description

大家一定觉的运动以后喝可乐是一件很惬意的事情,但是seeyou却不这么认为。因为每次当seeyou买了可乐以后,阿牛就要求和seeyou一起分享这一瓶可乐,而且一定要喝的和seeyou一样多。但seeyou的手中只有两个杯子,它们的容量分别是N 毫升和M 毫升 可乐的体积为S (S<101)毫升 (正好装满一瓶) ,它们三个之间可以相互倒可乐 (都是没有刻度的,且 S==N+M,101>S>0,N>0,M>0) 。聪明的ACMER你们说他们能平分吗?如果能请输出倒可乐的最少的次数,如果不能输出"NO"。

Input

三个整数 : S 可乐的体积 , N 和 M是两个杯子的容量,以"0 0 0"结束。

Output

如果能平分的话请输出最少要倒的次数,否则输出"NO"。

Sample Input

7 4 3
4 1 3
0 0 0

Sample Output

NO
3

样例的输出结果解析:
设s是瓶子(我看做大杯子),a、b两个杯子

s=4a=1b=3
400
103
112
202

所以三次的腾挪,可以让S中的可乐平分

思路解析

虽然知道这个是BFS题,但是仅仅对DFS有一点了解的情况下,实在是不清楚怎么做的,既然是广度搜索,那如何记录深度呢?要知道DFS可是用参数记录的
经过学长讲解,可以用队列的先进先出的性质结合结构体来做DFS

第一步,根据题意建立结构体和jud数组:
typedef struct {
 int s;//表示瓶子(大杯子)
 int a, b;//两个小杯子
 int t;//表示层数,与DFS的参数对应
}H;

刚听完自己做的时候只定义了s、a、b。。。最精髓的t没有定义,导致走了不少弯路
我们可以把三个杯子每种可能的容量叫做状态,就是说,最开始的状态就是
瓶子容量等于s,两个小杯子为0,我们需要按照数据规模开个乡音的bool数组防止两个杯子之间反复倒来倒去的存在(其实不一定是两个杯子倒来倒去,有可能有其他组合也能组成永动不停的情况,举个最简单的例子利于以后的自己理解。)
代码理解:

//操作
if(操作后形成的状态以前没有){
	标记该状态(jud对应数组值为1)
	加入队列
	标记层次
}
第二步:规定边界,这题边界很好想:就是存在两个杯子的容量相同
//操作前:s代表总量,当前s代表的是当前大杯子容纳的
if((当前s==当前a )&& 当前s==s/2||当前b==当前a && 当前b==s/2||当前s==当前b && 当前s==s/2 )
//停止广搜
第三步:思考什么规定成全局变量:

很显然,三个杯子的总容量和可乐总量比较适合作为全局变量(因为状态合不合理需要靠这三个量评判,不能出现一个杯子装不下的情况出现,这段话作为下面的剪枝环节的铺垫)

第四步:把初始状态装入队列头中去

这一步可以当做BFS的初始化来做。

然后就是考虑BFS的思考规律

DFS就是递归到底,直到没法递归才返回上一层,而BFS需要把一层的每个点都走一遍,一开始真的搞不懂该怎么做,STL的理解还是太浅既然用了队列+结构体成员表示层数,就不在需要用递归的参数去表示层数了
那么。。BFS的参数到底代表啥?
想着想着。。。。我发现,DFS之所以要用全局变量表示层数,是因为为了保证DFS参数只有一个(其实参数有一个两个貌似不太重要,重要的是DFS思想本身,不过感觉DFS写成一个参数思路更加清晰明了,所以个人比较喜欢用,其实回头来想,DFS也可以把边界当参数带进函数中,不过被使用的次数不想BFS这么频繁罢了,仅仅只是开头的) 在只有一个参数的情况下,不得不用全局变量来让局部的DFS去控制递归层数凡是作为边界的变量,本质上就是一个作用域包括了在某个过程的常量罢了,在这个过程中,他是一个铁律!不能被打破的铁律!(如果有某些变态的题需要拓宽边界这话就当我没说)

那么干脆让BFS的参数作为边界把,第三步重新思考!

int main() {
 int s, a, b;
 while(s = read(), a = read(), b = read()) {//读入挂接收数据
  if (s == a && a == b && b == 0) {//数据结束的标志
   break;
  }
  BFS(s,a,b);//把边界传进去
  while (!Q.empty()) {//队列没有clear,无语了
   Q.pop();
  }
  JUD = false;//全局的JUD,是否找到边界状态的标志,总不能参数带一个jud把,显得BFS的定义太臃肿了
  memset(jud,0,sizeof(jud));//清零
 }
}

可喜可贺,主函数的定义终于写出来了

DFS是递归,BFS是循环

BFS的注意点:
1、队列定义成全局的!
2、用初状态初始化队列!

、#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
typedef long long ll;
bool jud[101][101][101];
inline ll read() {//读入挂
 ll c = getchar(), Nig = 1, x = 0;
 while (!isdigit(c) && c != '-')c = getchar();
 if (c == '-')Nig = -1, c = getchar();
 while (isdigit(c))x = ((x << 1) + (x << 3)) + (c ^ '0'), c = getchar();
 return Nig * x;
}
inline void Out(ll a)
{//输出挂
 if (a < 0)putchar('-'), a = -a;
 if (a >= 10)Out(a / 10);//记得这是a>=10,让坑坏了
 putchar(a % 10 + '0');
}
typedef struct {
 int s;//表示瓶子(大杯子)
 int a, b;//两个小杯子
 int t;//表示层数,与DFS的参数对应
}H;
H  h1, h2;//一个是存起始状态的,一个是动态的
int time;
bool JUD;
queue<H>Q;//队列
void BFS(int s, int a, int b) {
 h2.s = s;//队列头状态的初始化,(s,0,0)
 h2.a = 0;
 h2.b = 0;
 h2.t = 0;//层数
 Q.push(h2);
 jud[h2.s][h2.a][h2.b] = true;
 while (!Q.empty()) {
  h1 = Q.front();
  Q.pop();
  if (s % 2 == 1)//优化,如果是奇数,肯定不能平分,直接跳出循环,输出NO
   break;
  if ((h1.s == h1.a && h1.s== s / 2) || (h1.a == h1.b && h1.b == s / 2) || (h1.b == h1.s && h1.s == s / 2)) {//
   //边界:有某个情况下有两个杯子的容量相等而且等于总量的一半
   JUD = true;//找到的标志位
   break;
  }
  //s->a
  if (h1.s) {//s有可乐,有可乐才能倒嘛
   if (h1.s + h1.a > a) {//s倒a有余或者说s不能全倒进a不然会漾出来
    h2.s = h1.s - (a - h1.a);//s减少a-当前的a
    h2.a = a;//a会满
   }
   else {//s能倒空
    h2.s = 0;
    h2.a = h1.a + h1.s;//以前的a加上以前s等于现在的a
   }
   h2.b = h1.b;
   if (!jud[h2.s][h2.a][h2.b]) {//如果不存在过该状态
    h2.t = h1.t + 1;//层数加一
    Q.push(h2);//把h2加入队列
    jud[h2.s][h2.a][h2.b] = true;//标记
   }
  }
  //s->b,上面的复制过来,a变成b,b变成a就行了
  if (h1.s) {//s有可乐,有可乐才能倒嘛
   if (h1.s + h1.b > b) {//s倒b有余或者说s不能全倒进b不然会漾出来
    h2.s = h1.s - (b - h1.b);//s减少b-当前的b
    h2.b = b;//b会满
   }
   else {//s能倒空
    h2.s = 0;
    h2.b = h1.b + h1.s;//以前的a加上以前s等于现在的a
   }
   h2.a = h1.a;
   if (!jud[h2.s][h2.a][h2.b]) {//以前没有这个状态
    h2.t = h1.t + 1;//层数加一
    Q.push(h2);//进入队列
    jud[h2.s][h2.a][h2.b] = true;//标记该状态已经出现过
   }
  }
  //a->b
  
  //a->s
  
  //b->a
  
  //b->s
  
 }
 if (JUD) {//证明找到边界状态
  Out(h1.t);
  cout << endl;
 }
 else
  cout << "NO" << endl;
}
int main() {
 int s, a, b;
 while(s = read(), a = read(), b = read()) {
  if (s == a && a == b && b == 0) {
   break;
  }
  BFS(s,a,b);
  while (!Q.empty()) {
   Q.pop();
  }
  JUD = false;//全局的JUD,是否找到边界状态的标志,总不能参数带一个jud把,显得BFS的定义太臃肿了
  memset(jud,0,sizeof(jud));//清零
 }
}

通过本题总结一些BFS的特点:
1、用参数做边界
2、用while代替递归,这也是它跟DFS的一个区别
3、BFS大致可分解成{
规定边界
初始化队列
进行操作
判断操作是否有效
有效操作的结果入队
边界的响应(输出、判断啥的)
}
思路有点乱,等待后续完善

总的代码(略长,了解思路的话上面就够了):

#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
typedef long long ll;
bool jud[101][101][101];
inline ll read() {//读入挂
 ll c = getchar(), Nig = 1, x = 0;
 while (!isdigit(c) && c != '-')c = getchar();
 if (c == '-')Nig = -1, c = getchar();
 while (isdigit(c))x = ((x << 1) + (x << 3)) + (c ^ '0'), c = getchar();
 return Nig * x;
}
inline void Out(ll a)
{//输出挂
 if (a < 0)putchar('-'), a = -a;
 if (a >= 10)Out(a / 10);//记得这是a>=10,让坑坏了
 putchar(a % 10 + '0');
}
typedef struct {
 int s;//表示瓶子(大杯子)
 int a, b;//两个小杯子
 int t;//表示层数,与DFS的参数对应
}H;
H  h1, h2;//一个是存起始状态的,一个是动态的
int time;
bool JUD;
queue<H>Q;//队列
void BFS(int s, int a, int b) {
 h2.s = s;//队列头状态的初始化,(s,0,0)
 h2.a = 0;
 h2.b = 0;
 h2.t = 0;//层数
 Q.push(h2);
 jud[h2.s][h2.a][h2.b] = true;
 while (!Q.empty()) {
  h1 = Q.front();
  Q.pop();
  if (s % 2 == 1)//优化,如果是奇数,肯定不能平分,直接跳出循环,输出NO
   break;
  if ((h1.s == h1.a && h1.s== s / 2) || (h1.a == h1.b && h1.b == s / 2) || (h1.b == h1.s && h1.s == s / 2)) {//
   //边界:有某个情况下有两个杯子的容量相等而且等于总量的一半
   JUD = true;//找到的标志位
   break;
  }
  //s->a
  if (h1.s) {//s有可乐,有可乐才能倒嘛
   if (h1.s + h1.a > a) {//s倒a有余或者说s不能全倒进a不然会漾出来
    h2.s = h1.s - (a - h1.a);//s减少a-当前的a
    h2.a = a;//a会满
   }
   else {//s能倒空
    h2.s = 0;
    h2.a = h1.a + h1.s;//以前的a加上以前s等于现在的a
   }
   h2.b = h1.b;
   if (!jud[h2.s][h2.a][h2.b]) {//如果不存在过该状态
    h2.t = h1.t + 1;
    Q.push(h2);//把h2加入队列
    jud[h2.s][h2.a][h2.b] = true;//标记
   }
  }
  //s->b,上面的复制过来,a变成b,b变成a就行了
  if (h1.s) {//s有可乐,有可乐才能倒嘛
   if (h1.s + h1.b > b) {//s倒b有余或者说s不能全倒进b不然会漾出来
    h2.s = h1.s - (b - h1.b);//s减少b-当前的b
    h2.b = b;//b会满
   }
   else {//s能倒空
    h2.s = 0;
    h2.b = h1.b + h1.s;//以前的a加上以前s等于现在的a
   }
   h2.a = h1.a;
   if (!jud[h2.s][h2.a][h2.b]) {//以前没有这个状态
    h2.t = h1.t + 1;
    Q.push(h2);
    jud[h2.s][h2.a][h2.b] = true;
   }
  }
  //a->b
  if (h1.a) {
   if (h1.a + h1.b > b) {
    h2.a = h1.a - (b - h1.b);
    h2.b = b;
   }
   else {//a能倒空
    h2.a = 0;
    h2.b = h1.b + h1.a;
   }
   h2.s = h1.s;
   if (!jud[h2.s][h2.a][h2.b]) {
    h2.t = h1.t + 1;
    Q.push(h2);
    jud[h2.s][h2.a][h2.b] = true;
   }
  }
  //a->s
  if (h1.a) {
   if (h1.a + h1.s > s) {
    h2.a = h1.a - (s - h1.s);
    h2.s = s;
   }
   else {
    h2.a = 0;
    h2.s = h1.a + h1.s;
   }
   h2.b = h1.b;
   if (!jud[h2.s][h2.a][h2.b]) {
    h2.t = h1.t + 1;
    Q.push(h2);
    jud[h2.s][h2.a][h2.b] = true;
   }
  }
  //b->a
  if (h1.b) {
   if (h1.a + h1.b > a) {
    h2.b = h1.b - (a - h1.a);
    h2.a = a;
   }
   else {
    h2.b = 0;
    h2.a = h1.b + h1.a;
   }
   h2.s = h1.s;
   if (!jud[h2.s][h2.a][h2.b]) {
    h2.t = h1.t + 1;
    Q.push(h2);
    jud[h2.s][h2.a][h2.b] = true;
   }
  }
  //b->s
  if (h1.b) {
   if (h1.s + h1.b > s) {
    h2.b = h1.b - (s - h1.s);
    h2.s = s;
   }
   else {
    h2.b = 0;
    h2.s = h1.b + h1.s;
   }
   h2.a = h1.a;
   if (!jud[h2.s][h2.a][h2.b]) {
    h2.t = h1.t + 1;
    Q.push(h2);
    jud[h2.s][h2.a][h2.b] = true;
   }
  }
 }
 if (JUD) {//证明找到边界状态
  Out(h1.t);
  cout << endl;
 }
 else
  cout << "NO" << endl;
}
int main() {
 int s, a, b;
 while(s = read(), a = read(), b = read()) {
  if (s == a && a == b && b == 0) {
   break;
  }
  BFS(s,a,b);
  while (!Q.empty()) {
   Q.pop();
  }
  JUD = false;//全局的JUD,是否找到边界状态的标志,总不能参数带一个jud把,显得BFS的定义太臃肿了
  memset(jud,0,sizeof(jud));//清零
 }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值