天天看点

UVA 10603 FILL 隐式图搜索

题意:有三个杯子,每个杯子所装水的容量为A,B,C,第三个杯子是的水满的,其他杯子都是空的。给出目标容量D,希望用最少的倒水量使其中的某个杯子所装水的量为D。如果无法达到目标容量,那就求出尽可能靠近目标容量的D‘需要的最少倒水量。

思路:隐式图搜索。我们将三个杯子现在的实际装水量和倒水量的总和作为一个状态。因为这里要求出的是倒水量最少的方案,而不是倒水次数最少的方案,正常的bfs的方法就不行了。这里我们需要将bfs中的队列换成优先队列,每次取出倒水量最少的状态进行扩展。

注意:对于这样的暴力搜索,我们要考虑对应的算法是否高效,我们就要计算搜索节点的个数。对于这道题,我们可以得到,因为前后三个杯子总的水量是没有发生变化的,所以只有两个杯子的水量是自由变量,所以整个状态数为:201 * 201 = 40401,这是相当小的状态。所以这样搜索是非常高效的。

          同时,对于不同的要求,我们需要不同的搜索策略。如果只是求最少倒水次数,对应状态转移图中,状态转移的代价为1.只需正常的bfs即可。而本题是要求最少的倒水量,对应在状态转移图中,状态转移的代价是由当前的状态决定的,而求出这种情况下的“最短路”就需要利用优先队列了。

代码如下:

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>

using namespace std;

const int MAX = 210;

int A,B,C,D;
bool vis[MAX][MAX];
int ans[MAX];
int cap[MAX];

struct node{
    int v[3];
    int dist;
    node(){}
    node(int aa, int bb, int cc,int dd):dist(dd){
        v[0]=aa,v[1]=bb,v[2]=cc;
    }
    bool operator < (const node & rhs) const{
        return dist > rhs.dist;
    }
};


int main(void)
{
    //freopen("input.txt","r",stdin);
    int T;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d%d%d",&A,&B,&C,&D);
        cap[0] = A, cap[1] = B, cap[2] = C;
        memset(vis,0,sizeof(vis));
        memset(ans,0x3f,sizeof(ans));
        priority_queue<node> Q;
        Q.push(node(0,0,C,0));
        while(!Q.empty()){
            node t = Q.top();Q.pop();
            if(vis[t.v[0]][t.v[1]]) continue;
            vis[t.v[0]][t.v[1]] = true;
            for(int i = 0; i < 3; ++i){
                int d = t.v[i];
                ans[d] = min(ans[d],t.dist);
            }
            if(ans[D] != 0x3f3f3f3f) break;
            for(int i = 0; i < 3; ++i){
                for(int j = 0; j < 3; ++j){
                    if(j != i){
                        if(t.v[i] == 0 || t.v[j] == cap[j]) continue;
                        int amo  = min(cap[j], t.v[i] + t.v[j]) - t.v[j];
                        node p = t;
                        p.dist += amo;
                        p.v[i] -= amo, p.v[j] += amo;
                        Q.push(p);
                    }
                }
            }
        }
        for(int i = D; i >= 0; --i){
            if(ans[i] != 0x3f3f3f3f){
                printf("%d %d\n",ans[i],i);
                break;
            }
        }
    }
    return 0;
}