非常可乐(九度 OJ 1457)

非常可乐(九度 OJ 1457)

时间限制:1 秒 内存限制:32 兆 特殊判题:否

1.题目描述:

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

输入:
三个整数 : S 可乐的体积 , N 和 M 是两个杯子的容量,以"0 0 0"结束。
输出:
如果能平分的话请输出最少要倒的次数,否则输出"NO"。

样例输入:
7 4 3
4 1 3
0 0 0
样例输出:
NO
3

2.基本思路

刚看到题,乍一看这不是当年数模校赛的分酒问题吗?!又想起了当年在虹桥机场的那场泊松分酒盛宴。记得当初的问题问的是如何利用最少的步骤来平分酒,我们首先将所有的状态用图存了起来,然后用Dijkstra算法求得单源最短路径。当初可能主要是用了一大堆面向对象的方法来定义节点类,边类等等,导致问题有点繁杂,现在回过头来看还是可以用较短的代码就可以解决的。
扯当年,扯得有点远了,好吧,言归正传。
首先这是一个搜索的问题。这里我们采用BFS的方式来进行搜索,每一个状态下面最多有六种状态,即可以扩展六个结点,即(S->M,S->N,M->N,M->S,N->M,N->S这六种情况)。每次扩展之前需要先检查该状态是否可以扩展,或该状态是否已经被访问过了,以此来进行适当地剪枝。求解的过程有一些细节需要注意:首先就是引用传递,可以节省变量的创建,使得代码更加简洁。还有就是在多组测试用例的情况下,没运行一组之前都要把有关记录状态信息的存储结构恢复初始状态。
还有这个题目其实有些地方没讲清楚:

  • ①可乐被平分是指要完全分完吗,还是只要保证两个人分到的是一样的就可以了,不用管是否还有多余的。这个根据例题所给的代码来看是S必须被平分为两个 S 2 \frac{S}{2} 2S才可以的,那么S,M,N为整数那么可以断定知道S%2!=0就一定不能被平分。

3.代码实现

#include <iostream>
#include <queue>
#define NUM 100

using namespace std;

struct Node{
    int s,n,m;//三个杯子中可乐的体积
    int t;//到达该状态耗费的步数
};

queue<Node> Q;//存储状态结点
bool mark[NUM][NUM][NUM]={false};//用于表示某一种状态是否已经被搜素过了
int S,N,M;//三个杯子的容积
bool _find= false;

void pour(int a,int& ac,int b,int& bc){//倒可乐a->b,ac为杯子a中当前可乐的体积,bc为杯子b中当前可乐的体积
    if((b-bc)>ac){//a可以全倒过去
        bc = bc + ac;
        ac = 0;
    }
    else{//a只能倒一部分
        ac = ac-(b-bc);//注意,两句话的顺序不能反,找了好久的Bug。。
        bc = b;
    }
}

int main()
{
    while(scanf("%d%d%d",&S,&N,&M)!=EOF){
        if(S==0&&N==0&&M==0)break;

        //记得每次开始前要清空上一个case遗留的信息
        for(int i=0;i<S;i++){
            for(int j=0;j<N;j++){
                for(int k=0;k<M;k++){
                    mark[i][j][k]=false;
                }
            }
        }
        while(Q.empty()==false){//初始时先清空队列
            Q.pop();
        }
        _find = false;//标记是否有解

        Node n;
        n.s = S;
        n.m = 0;
        n.n = 0;
        n.t = 0;

        mark[S][0][0]=true;
        Q.push(n);

        while(Q.empty()==false){
            Node node = Q.front();//取队首结点
            Q.pop();
            printf("s=%d,n=%d,m=%d,t=%d\n",node.s,node.n,node.m,node.t);
            if(node.m==S/2&&node.n==S/2||node.m==S/2&&node.s==S/2||node.n==S/2&&node.s==S/2){//判断是否达到终止条件
                    _find = true;
                    printf("%d\n",node.t);
                    break;
            }//实际上该过程可以在倒完就进行判断,不要再加入到队列中,等取出来的时候再进行判断。这里为了精简代码,做此处理

            //倒酒的过程,有6种情况
            //case1:M->N
//            printf("node address:%d\n",&node);
            Node node1;
            node1= node;
//            printf("node1 address:%d\n",&node1);
            if(node.m>0){
                pour(M,node1.m,N,node1.n);
                if(mark[node1.s][node1.n][node1.m]==false){
                    node1.t++;
                    Q.push(node1);
                    mark[node1.s][node1.n][node1.m]=true;
                }
            }

             //case2:M->S
            Node node2;
            node2= node;
            if(node.m>0){
                pour(M,node2.m,S,node2.s);
                if(mark[node2.s][node2.n][node2.m]==false){
                    node2.t++;
                    Q.push(node2);
                    mark[node2.s][node2.n][node2.m]=true;
                }
            }

            //case3:N->M
            Node node3;
            node3= node;
            if(node.n>0){
                pour(N,node3.n,M,node3.m);
                if(mark[node3.s][node3.n][node3.m]==false){
                    node3.t++;
                    Q.push(node3);
                    mark[node3.s][node3.n][node3.m]=true;
                }
            }

            //case4:N->S
            Node node4;
            node4= node;
            if(node.n>0){
                pour(N,node4.n,S,node4.s);
                if(mark[node4.s][node4.n][node4.m]==false){
                    node4.t++;
                    Q.push(node4);
                    mark[node4.s][node4.n][node4.m]=true;
                }
            }

            //case5:S->M
            Node node5;
            node5= node;
            if(node.s>0){
                pour(S,node5.s,M,node5.m);
                if(mark[node5.s][node5.n][node5.m]==false){
                    node5.t++;
                    Q.push(node5);
                    mark[node5.s][node5.n][node5.m]=true;

                }
            }

            //case6:S->N
            Node node6;
            node6= node;
            if(node.s>0){
                pour(S,node6.s,N,node6.n);
                if(mark[node6.s][node6.n][node6.m]==false){
                    node6.t++;
                    Q.push(node6);
                    mark[node6.s][node6.n][node6.m]=true;
                }
            }

        }
        if(_find==false)
            printf("NO\n");//

    }
    return 0;
}
/*
7 4 3
4 1 3
0 0 0
*/

以下代码为讨论区里面有人共享的,利用数学运算的规律求解该问题,可读性比较差:

#include <cstdio>

int gcd(int x, int y)
{
    return y ? gcd(y, x % y) : x;
}

int main()
{
    int a, b, c;
    
    while (scanf("%i%i%i", &a, &b, &c), a + b + c) {
        (a /= gcd(b, c)) & 1 && ~puts("NO") || printf("%i\n", a - 1);
    }
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值