题意:有三个杯子,每个杯子所装水的容量为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;
}