/**********************************************************

2017.1.24

重新做了一遍这个题目。

三个杯子,总水量是一样的,只需要记录前两个杯子来作为状态来判重即可。

然后他要求总到水量最小,直接贪心就好了,优先选取总水量最小的,用优先队列即可。

然后找一个最接近d的答案即可。


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

using namespace std;
struct Node{
    int v[3];
    int val;
    Node(){
        val = 0;
        memset(v,0,sizeof v);
    }
    bool operator < (const Node& rhs) const {
        return val > rhs.val;
    }
}st;

priority_queue<Node> q;
bool vis[207][207];
int all;
int ans1,ans2;
int a,b,c,d;
int va[5];
void bfs(){
    while(!q.empty())q.pop();
    memset(vis,0,sizeof vis);
    q.push(st);
    vis[st.v[0] ][st.v[1]] = 1;
    while(!q.empty()){
        Node u = q.top(); q.pop();
        for (int i = 0; i < 3; ++i){
            if (u.v[i] <= d){
                if (u.v[i] > ans1){
                    ans1 = u.v[i];
                    ans2 = u.val;
                    if (ans1 == d) return ;
                }
            }
        }

        for (int i = 0; i < 3; ++i){
            for (int j = 0; j < 3; ++j){ ///i -> j;
                if (i != j && u.v[j] != va[j] && u.v[i] != 0){
                    int lef = va[j] - u.v[j];
                    int hav = u.v[i];
                    Node ne = Node();
                    for (int k = 0; k < 3; ++k) ne.v[k] = u.v[k];
                    if (hav >= lef){
                        ne.v[i] = hav - lef;
                        ne.v[j] = va[j];
                        ne.val = u.val + lef;
                    }
                    else {
                        ne.v[i] = 0;
                        ne.v[j] += hav;
                        ne.val = u.val + hav;
                    }
                    if (!vis[ne.v[0]][ne.v[1]]){
                        vis[ne.v[0]][ne.v[1]] = 1;
                        q.push(ne);
                    }

                }

            }

        }
    }
}
int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        ans1 = -0x3f3f3f3f;
        st = Node();
        scanf("%d %d %d %d",&a, &b, &c, &d);
        va[0] = a; va[1] = b; va[2] = c;
        all = a + b + c;
        st.v[0] = 0; st.v[1] = 0; st.v[2] = c; st.val = 0;
        bfs();
        printf("%d %d\n",ans2,ans1);
    }
    return 0;
}









完全仿照紫书上来写的!

书中大体思路是:

用ans[]来记录答案,不断取最小值来更新!

用vis[][]来表示是否访问过,之所以是二维数组,是因为总水量是固定的,两个杯子确定,第三个杯子自然也就确定,两个状态足矣!

用结构体表示每一个状态!其中包括每个杯子的水量!u.wat[],还有dist 为到目前这个状态总的取水量!

然后用优先队列不断倒水,

有一个技巧,代码中先算出需要倒的水量,然后再计算!这样结果不会出现负值,两个情况都考虑到了!很巧妙!

int m = min(cup[j],u.wat[i]+u.wat[j]) - u.wat[j];//m 为需要倒水的量,这样写 不会出现负值!


最后注意结果可能会有0就行了!

#include<bits/stdc++.h>
using namespace std;
struct Node{
    int wat[3],dist;//wat[]是各个杯子里的水,dist是总取水量!
    bool operator < (const Node &rhs) const {
        return dist > rhs.dist;//最小值优先级大!
    }
};
const int maxn = 200 + 10;
int cup[3],vis[maxn][maxn],ans[maxn];//vis[][]之所以是二维数组,是因为总水量是固定的,两个杯子确定,第三个杯子自然也就确定,两个状态足矣!
void update_ans(const Node &u){
    for (int i = 0; i < 3; ++i){
        int d = u.wat[i];
        if (ans[d] < 0 || u.dist < ans[d])ans[d] = u.dist;//在优先队列中每提出一个队首元素,就对应一个新的状态,就需要在ans[]更新状态!更新取水量最小的状态
    }
}
void solve(int a,int b,int c,int d){
    memset(ans,-1,sizeof(ans));
    memset(vis,0,sizeof(vis));
    cup[0] = a; cup[1] = b; cup[2] = c;//初始化杯子的容量!
    priority_queue<Node>q;
    Node u;
    u.wat[0] = u.wat[1] = 0; u.wat[2] = c;
    u.dist = 0;
    q.push(u);
    while(!q.empty()){
        u = q.top(); q.pop();
        update_ans(u);
        if (ans[d] >= 0)break;
        for (int i = 0; i < 3; ++i)//i倒入j
            for (int j = 0; j < 3; ++j)
                if (i != j){
                    if (cup[i] <= 0 || u.wat[j] == cup[j])continue;
                    int m = min(cup[j],u.wat[i]+u.wat[j]) - u.wat[j];//m 为需要倒水的量,这样写 不会出现负值!
                    Node v = u;
                    v.dist += m;
                    v.wat[i] -= m;
                    v.wat[j] += m;
                    int key1 = v.wat[0],key2 = v.wat[1];//key1,key2为前两个杯子的状态!
                    if (vis[key1][key2] == 0){//未访问过这个状态!
                        vis[key1][key2] = 1;
                        q.push(v);
                    }
                }
    }
    while(d >= 0){//注意有等号,结果可能会有0,就是一开始就符合!
        if (ans[d] >= 0){printf("%d %d\n",ans[d],d);return;}
        --d;
    }
}
int main()
{
    int T,a,b,c,d;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d%d%d",&a,&b,&c,&d);
        solve(a,b,c,d);
    }
    return 0;
}