1. 程式人生 > >HDU 1495 非常可樂(不一樣的bfs)

HDU 1495 非常可樂(不一樣的bfs)

非常可樂

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 25387    Accepted Submission(s): 9877


 

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 

 

 

思路:有一個大神寫的blog已經很好了 ,我就直接轉載一下啦

原文地址:https://blog.csdn.net/qq_34374664/article/details/51646060

思考過程:這道題我一開始按照題意以及樣例用腦子模擬了一遍(可能我模擬的方式不對誤導了我),感覺應該是用深搜,類似於全排列,就是一個杯子倒過來到過去,後來寫了寫,寫到一半寫不下去了,卡住了。看了看網上的程式碼,普遍很長,而且用的是廣搜,我一直不理解為什麼可以用廣搜,他們解釋的是一共就有6個狀態a1-a2,a2-a1,a1-a3,a3-a1,a2-a3,a3-a2,這六個狀態每次都做一邊就好了。。我當時是懵逼的。。我以為他就是按照這個順序,每倒一次就step+1,那樣肯定不對啊。。後來我就照著網上的程式碼寫(不理解題意但是程式碼知道什麼意思),做了一些優化,用兩個for迴圈做控制倒水。那天問了問斌神,他跟我說了說,我豁然開朗,原來這6個狀態算“一步”,不是倒一次水算一步。而且最少最短這種等權問題一般都用廣搜。。

總結:1,深刻理解廣搜,以前總是侷限於地圖,迷宮的問題,這題也可以用廣搜,以後求最短,最快,最少的問題(等權)都可以往廣搜上面想想。。。理解每個”step“的含義,這裡每一個step就是在正確的倒水路徑上的每一次倒水,一共有6次倒水的方法,讓每個“step”做這6個“動作”,然後一直這樣往下”各個方向瀰漫“,就一定會找到答案。

2.以前總是做一些地圖迷宮的問題,思想拘束在”走一步這個動作“就是一個step,把思維開啟,一個step可以有”n個動作“(一定是所有可能性都包含),而且這n個動作是平行的。這也是這題的關鍵。

3.自己還是刷題太少了,接觸的太少了,思維太侷限,而且還有一些輸出的小細節自己可能也注意不到,感謝”斌大腿“的指教與點播,發現自己與他的差距還是很大。。
 

#include <queue>
#include <cstring> //用來給陣列賦初值為0的,memset;
using namespace std;
int b[3],book[101][101][101],half;
struct node
{
    int cur[3],s;          //cur表示現在瓶子裡的水,b表示水的容量
}p,temp;          //p獲取當前的“head”,temp用於6個動作的記錄。
void bfs()
{
    queue<node> q;         //宣告一個佇列,由於經常做pat乙  只有一組資料習慣宣告全域性變數,如果把這句放在bfs()外邊,因為有多組
    p.cur[0]=b[0];          //測試資料,如果不把之前佇列裡的資料清空就會帶到下一組測試中(做題考慮下這個機制會不會對某些題目有用)
    p.cur[1]=p.cur[2]=p.s=0;  //放到bfs裡面  每次佇列都是新的。
    q.push(p);                  //將最開始狀態初始化 加入佇列q中;
    while(!q.empty())
    {
        p=q.front();
        q.pop();
        for(int i=0;i<3;i++)     //控制哪個杯子往外倒水
        {
            if(p.cur[i]>0)      //如果有水才可以給別的倒水。
            {
                for(int j=0;j<3;j++)   //接受水的杯子
                {
                    temp=p;            //這很重要!之前沒加這句一直wa,因為這6個動作是”平行“的,即都是從前一步狀態開始的,如果沒有
                    if(i==j)  continue ;   //這一句,就不是平行的,相互疊加的。這個很重要!
                    if(temp.cur[i]>b[j]-temp.cur[j])   //判斷能不能倒滿了,這是可以倒滿的情況
                    {
                        temp.cur[i]-=b[j]-temp.cur[j];      //這兩句話一定不要顛倒,之前因為這個一直不出答案,先運算後賦值。。。
                        temp.cur[j]=b[j];
                    }
                    else     // 不可以倒滿的情況
                    {
                        temp.cur[j]+=temp.cur[i];
                        temp.cur[i]=0;
                    }
                    if(!book[temp.cur[0]][temp.cur[1]][temp.cur[2]])  //判斷這種情況是否出現過
                    {
                        book[temp.cur[0]][temp.cur[1]][temp.cur[2]]=1;  //標記為出現過,說明這一步“有效”,step+1;
                        temp.s++;
                        if((temp.cur[0]==half&&temp.cur[1]==half)||(temp.cur[0]==half&&temp.cur[2]==half)||(temp.cur[1]==half&&temp.cur[2]==half))
                            {
                                cout << temp.s << endl;  // step裡的每一個“動作”都要判斷是否符合條件,因為這動作是平行的 所以放在內迴圈裡面!
                                return ;             //直接跳出bfs
                            }
                            q.push(temp);// 如果不是所求,就把他加到佇列裡。之後在從每一個“動作呼叫”
                    }
                }
            }
        }
 
 
    }
      cout <<"NO"<<endl;  //如果整個迴圈結束還是沒有找到說明不可以平分;
}
int main()
{
    while(cin >> b[0]>>b[1]>>b[2],b[0]+b[1]+b[2])   //如果相加都等於0 即000 用於結束
    {
        memset(book,0,sizeof(book));  //多組資料每一組都要賦初值,想用book【】【】={0},必須要在宣告book陣列時候用;
        book[b[0]][b[1]][b[2]]=1;   //把初始點標記來過,這一點在呼叫bfs之前做 很多題都要注意這一點。
        if(b[0]%2)  cout << "NO"<<endl;
        else {half=b[0]/2;bfs();}
    }
    return 0;
}