UVA 10603 Fill(狀態空間搜索,倒水問題)

【題目鏈接】uva-10603

【題意】設3個杯子的容量分別爲a, b, c,最初只有第3個杯子裝 滿了c升水,其他兩個杯子爲空。最少需要倒多少升水才能讓某一個杯子中的水有d升呢?如果無法做到恰好d升,就讓某一個杯子裏的水是d'升,其中d'<d並且儘量接近d。 (1≤a,b,c,d≤200)。要求輸出最少的倒水量和目標水量(d或者d')。

【樣例】

Sample Input
2
2 3 4 2
96 97 199 62
Sample Output
2 2
9859 62

【分析】

      (劉汝佳說的夠清楚了…直接複製了書上的分析)

       假設在某一時刻,第1個杯子中有v0升水,第2個杯子中有v1升水,第3個杯子中有v2升水,稱當時的系統狀態爲(v0,v1,v2)。

       這裏再次提到了“狀態”這個詞,它是理解很多概念和算法的關鍵。簡單地說,它就是“對系統當前狀況的描述”。例如,在國際象棋中,當前遊戲者和棋盤上的局面就是刻畫遊戲進程的狀態。

       把“狀態”想象成圖中的結點,可以得到如圖7-16所示的狀態圖(state graph)。

       由於無論如何倒,杯子中的水量都是整數(按照倒水次數歸納即可),因此第3個杯子的 水量最多隻有0, 1, 2,…, c共c+1種可能;同理, 第2個杯子的水量一共只有b+1種可能,第1個杯子一共只有a+1種可能,因此理論上狀態最多有 (a+1)(b+1)(c+1)=8120601種可能性,有點大。幸運的是,上面的估計是不精確的。由於水的總量x永遠不變,如果有兩個狀態的前兩個杯子的水量都相同,則第3個杯子的水量也相同。換句話說,最多可能的狀態數不會超過2012=40401。

       注意:本題的目標是倒的水量最少,而不是步數最少。實際上,水量最少時步數不一定最少,例如a=1, b=12, c=15, d=7,倒水量最少的方案是C->A, A->B重複7次,最後C裏有7 升水。一共14步,總水量也是14。還有一種方法是C->B,然後B->A, A->C重複4次,最後C裏有7升水。一共只有10步,但總水量多達20。因此,需要改一下算法:不是每次取出步數最少的結點進行擴展,而是取出水量最少的 結點進行擴展。這樣的程序只需要把隊列queue換成優先隊列priority_queue,其他部分的代碼不變。下面的代碼把狀態(三元組)和dist合起來定義爲了一個Node類型,是一種常見的寫法。

【總結】關於這道題,最大的感想是自己的代碼寫的真是太挫了,各種強行寫。寫完過後看了看劉汝佳的代碼……默默重寫了一遍。他寫的真好啊。學習…

【代碼】

#include<cstdio>
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=205;
int a,b,c,d;
struct node{
    int v[3],vol;
    bool operator< (const node& rhs) const{
        return vol>rhs.vol;
    }
};
int vis[maxn][maxn],ans[maxn],cap[3];
void update_ans(node u){
    for(int i=0;i<3;i++){
        int d=u.v[i];
        if(ans[d]<0)//||ans[d]>u.vol)
            ans[d]=u.vol;
    }
    return ;
}
priority_queue<node> p;
int bfs(){
    while(!p.empty()){
        node e=p.top();
        p.pop();
        vis[e.v[0]][e.v[1]]=true;
        update_ans(e);
        if(ans[d]>=0)
            break;
        for(int i=0;i<3;i++){//倒水的
            for(int j=0;j<3;j++){//被倒入的
                if(e.v[i]==0||e.v[j]==cap[j])
                    continue;
                int amt=min(cap[j],e.v[i]+e.v[j])-e.v[j];
                node s=e;
                s.vol+=amt;
                s.v[i]-=amt;
                s.v[j]+=amt;
                if(!vis[s.v[0]][s.v[1]]){
                    p.push(s);
                }
            }
        }
    }
    while(d>=0){
        if(ans[d]>=0){
            printf("%d %d\n",ans[d],d);
            return 0;
        }
        d--;
    }
    return 0;
}

int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d%d%d",&a,&b,&c,&d);
        memset(vis,false,sizeof(vis));
        memset(ans,-1,sizeof(ans));
        while(!p.empty())
            p.pop();
        node s;s.v[0]=0;s.v[1]=0;s.v[2]=c;s.vol=0;
        cap[0]=a;cap[1]=b;cap[2]=c;
        vis[0][0]=true;
        p.push(s);
        bfs();
    }
    return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章