L - 非常可乐——HDU-1495(bfs->优化bfs->数论)
非常可乐
原题链接:http://acm.hdu.edu.cn/showproblem.php?pid=1495
大家一定觉的运动以后喝可乐是一件很惬意的事情,但是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
题意:已经很明显了,我就没必要再说了,但u1s1,可乐自己一个人喝它不香吗?还有,要是see you 觉得喝可乐不惬意,那么直接给阿牛呗,题终!
第一种解题方案(最麻烦但最通俗易懂的):
由于是寻求最少倒水次数,所以我们应该使用bfs模拟六种情况搜索。分别是s->n;s->m;n->s;n->m;m->s;m->n.得到答案的条件其中两个剩余的量相同,剩下的一个为空。
由于此时的变化状态物有三个,所以我们要设置三维的辅助数组来判断某个状态是否已访问从而避免重复路径访问造成不必要的计算甚至死循环。同时设立结构体来存储某个状态的s,m,n和到达该状态的倒水次数。OK,此题就可以解决了。
PS:此题模拟六种情况的时候一定要细心,仔细分析判断。
AC代码:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<string>
#include<stack>
#include<queue>
#include<cstring>
#include<memory.h>
#include<map>
#include<iterator>
#include<list>
#include<set>
#include<functional>
using namespace std;
const int maxn=102;
struct node{
int s,n,m;//可乐瓶和两个杯子在当前状态的剩余容量。
int step;//倒到当前状态时所需次数。
};
bool visited[maxn][maxn][maxn];
int s,n,m;//可乐瓶的体积和两个杯子的容量。
void bfs(int s,int n,int m){
queue<node> q;
memset(visited,false,sizeof(visited));
node temp,head;
head.s=s;head.m=head.n=head.step=0;
visited[s][0][0]=true;
q.push(head);
while(!q.empty()){
head=q.front();
if((head.s==head.m&&head.n==0)||(head.s==head.n&&head.m==0)||(head.n==head.m&&head.s==0)){
cout<<head.step<<endl;
return;
}
q.pop();
for(int i=0;i<6;i++){
//六种情况
if(i==0){
//s->n
//足够
if(head.s>=n-head.n){
temp.n=n;
temp.s=head.s-(n-head.n);
temp.m=head.m;
}
//不够
else{
temp.n=head.n+head.s;
temp.s=0;
temp.m=head.m;
}
}
else if(i==1){
//s->m
//足够
if(head.s>=m-head.m){
temp.m=m;
temp.s=head.s-(m-head.m);
temp.n=head.n;
//足够
if(head.s>=m-head.m){
temp.m=m;
temp.s=head.s-(m-head.m);
temp.n=head.n;
}
//不够
else{
temp.m=head.m+head.s;
temp.s=0;
temp.n=head.n;
}}
//不够
else{
temp.m=head.m+head.s;
temp.s=0;
temp.n=head.n;
}
}
else if(i==2){
//n->s
//足够
if(head.n>=s-head.s){
temp.s=s;
temp.n=head.n-(s-head.s);
temp.m=head.m;
}
//不够
else{
temp.s=head.s+head.n;
temp.n=0;
temp.m=head.m;
}
}
else if(i==3){
//n->m
//足够
if(head.n>=m-head.m){
temp.m=m;
temp.n=head.n-(m-head.m);
temp.s=head.s;
}
//不够
else{
temp.m=head.m+head.n;
temp.n=0;
temp.s=head.s;
}
}
else if(i==4){
//m->s
//足够
if(head.m>=s-head.s){
temp.s=s;
temp.m=head.m-(s-head.s);
temp.n=head.n;
}
//不够
else{
temp.s=head.m+head.s;
temp.m=0;
temp.n=head.n;
}
}
else{
//m->n
//足够
if(head.m>=n-head.n){
temp.n=n;
temp.m=head.m-(n-head.n);
temp.s=head.s;
}
//不够
else{
temp.n=head.m+head.n;
temp.m=0;
temp.s=head.s;
}
}
if(!visited[temp.s][temp.n][temp.m]){
visited[temp.s][temp.n][temp.m]=true;
temp.step=head.step+1;
q.push(temp);
}
}
}
cout<<"NO"<<endl;
return;
}
int main() {
while(cin>>s>>n>>m&&(s+n+m)){
bfs(s,n,m);
}
return 0;
}
第二种解题方案(优化bfs,减少不必要的计算)
推理:
我们思考一下,对于一瓶 容量为s=n+m的可乐,最后分完的结果是什么?这些可乐都分到哪去了?如果把可乐瓶也想象成杯子,那么显然可乐瓶的杯子最大,我们编号为1,接下来对两个已有的两个杯子判断,容量较大的为2,容量较小的为3.则1>2>=3.那么我们假设最后得到的结果3中有可乐,那么必然其中也要有2或1中有可乐,且它们的量要相等。可这显然不现实,因为3容量中最小,即使它满了,那么加相等的那个量也就只有2倍的3的容量是小于s(s=n+m)的。故最后3中一定是空的,我们只要考虑1和2是否相等以及3是否为空。OK,这就是我们的推理,对杯子编号,排序。
优化:
有了这个推理以后,我们再看看一开始1号杯是满的,其他两个杯是空的,又因为最后结果是1号杯的量的等于2号杯的量且3号杯为空,则如果1号杯的容量是奇数,它就无法对半分,因为水的刻度为整数,没办法,故我们在开始判断一下1号杯的量是否为奇数即可。我们设置一个倒水函数,因为我们已经对杯子编号了,所以可以通用,同样在倒水的设计上我们可采用简化的方式。(详细见代码),在判断的过程中我们只要考虑1号杯子和2号杯子的量是否相等以及3号是否为空即可。
AC代码:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<string>
#include<stack>
#include<queue>
#include<cstring>
#include<memory.h>
#include<map>
#include<iterator>
#include<list>
#include<set>
#include<functional>
using namespace std;
const int maxn=102;
int v[4];//为了简单判断,使这些杯子有规律性,我们编号
struct node{
int v[4];//表示当前状态的三个杯子里的剩余量。
int step;//记录步骤
};
node temp,head;
bool visited[maxn][maxn][maxn];//辅助数组,判断某个状态是否已经访问
void pour(int a,int b){
//第a个杯子往第b个杯子里倒水。
int sum=temp.v[a]+temp.v[b];//
if(sum>=v[b]){
//如果总量大于第b个杯子的容量,则
temp.v[b]=v[b];
}
else{
//小于则直接等于总容量。
temp.v[b]=sum;
}
//最后第a个杯子剩余的容量。
temp.v[a]=sum-temp.v[b];
}
void bfs(){
//我们一定要v[1]>v[2]>=v[3]。
queue<node> q;
head.v[1]=v[1];head.v[2]=head.v[3]=head.step=0;
memset(visited,false,sizeof(visited));
visited[v[1]][0][0]=true;
q.push(head);
while(!q.empty()){
head=q.front();
q.pop();
if(head.v[1]==head.v[2]&&head.v[3]==0){
cout<<head.step<<endl;
return;
}
for(int i=1;i<4;i++){
for(int j=1;j<4;j++){
//模拟倒水情况。
if(i!=j){
//自己肯定不能给自己倒水,故这里排除杯子编号相等的情况。
temp=head;
pour(i,j);
if(!visited[temp.v[1]][temp.v[2]][temp.v[3]]){
visited[temp.v[1]][temp.v[2]][temp.v[3]]=true;
temp.step++;
q.push(temp);
}
}
}
}
}
cout<<"NO"<<endl;
}
int main() {
while(cin>>v[1]>>v[2]>>v[3]&&(v[1]+v[2]+v[3])){
if(v[1]%2){cout<<"NO"<<endl;continue;}//判断是否为奇数。
if(v[2]<v[3]){
//为了保证我们这个规律:v[1]>v[2]>=v[3]。
int t=v[3];
v[3]=v[2];
v[2]=t;
}
bfs();
}
return 0;
}
第三种解题方案(数论)
这种方法非常厉害,可惜我没看懂,但还是贴在这里吧。
分析:设两个小瓶子容积分别为a,b,问题转化成通过两个小瓶子的若干次倒进或倒出操作得到(a+b)/2体积的可乐,设两个小瓶子被倒进或倒出x次和y次(这里的x和y是累加后的操作,即x=第一个瓶子倒出的次数-倒进的次数,y=第二个瓶子倒出的次数-倒进的次数),那么问题转化成:
所以|x+|y|的最小值为(c+d)/2,通过x和y的通解形式显然可以看出x和y一正一负,不妨设x<0,那么就是往第一个小瓶子倒进x次,第二个小瓶子倒出y次,但是由于瓶子容积有限,所以倒进倒出操作都是通过大瓶子来解决的,一次倒进操作后为了继续使用小瓶子还要将小瓶子中可乐倒回大瓶子中,倒出操作同理,所以总操作次数是(c+d)/2*2=c+d,但是注意最后剩下的(a+b)/2体积的可乐一定是放在两个小瓶子中较大的那个中,而不是再倒回到大瓶子中,所以操作数要减一,答案就是c+d-1。
AC代码:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<string>
#include<stack>
#include<queue>
#include<cstring>
#include<memory.h>
#include<map>
#include<iterator>
#include<list>
#include<set>
#include<functional>
using namespace std;
int gcd(int a,int b){
return b==0?a:gcd(b,a%b);
}
int main(){
int a,b,c;
while(cin>>a>>b>>c&&(a&&b&&c))
{
a/=gcd(b,c);
if(a&1)
cout<<"NO"<<endl;
else
cout<<a-1<<endl;
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!