思路借鉴自《算法竞赛入门经典》刘汝佳 著
题目概要:设3个杯子的容量分别为a,b,c,一开始只有第3个杯子装满了c升水,其余杯子为空。需求求解最少需要倒多少升水才能让某个杯子中的水有d升。如果无法做到d升,则让杯子的水达到d‘升(d'尽量接近d)
分析:一道搜索的题,这里需要把杯子中水的量视作一个状态(a,b,c),然后倒水可以视作一次状态的改变(状态结点的扩展)
设某一状态A(a1,b1,c1)到另一状态B(a2,b2,c2),需倒水amount升。
起始状态(0,0,c)
终止状态(x,y,z){x,y,z中存在一个等于d,或者所有状态搜索完成后,有一个最接近d的d’}
现在我们可以把两个相邻状态A,B视作顶点v,u,
状态A与状态B之间的转移需要倒amount升水,
即edge<v,u>的权重为amount,因此整个状态转移过程可以构成一个有向图。
因此我们需要求解的问题,从而转化为求出起始状态顶点到终止状态顶点的最短路径问题,
从而可以用dijkstra算法求解。
下面是具体代码:
#include<cstring>
#include<stdio.h>
#include<iostream>
#include<queue>
using namespace std;
#define MAX 205
int vis[MAX][MAX], cap[3], ans[MAX];
int a, b, c, d;
class state
{
public:
int v[3], dist;
state() {}
bool operator <(const state& rhs) const
{
return this->dist > rhs.dist;
}
};
int min(int a, int b)
{
return a < b ? a : b;
}
void update_ans(const state& s)
{
for (int i = 0;i != 3;++i)
{
int d = s.v[i];
if (ans[d] < 0 || s.dist < ans[d])
{
ans[d] = s.dist;
}
}
}
void dijkstra()
{
priority_queue<state, vector<state> > pq;
state begin;
begin.v[0] = 0;
begin.v[1] = 0;
begin.v[2] = c;
begin.dist = 0;
pq.push(begin);
vis[0][0] = 1;
while (!pq.empty())
{
state next = pq.top();
pq.pop();
update_ans(next);
if (ans[d] > 0)
break;
for (int i = 0;i != 3;++i)
{
for (int j = 0;j != 3;++j)
{
if (i == j || next.v[i] == 0 || next.v[j] == cap[j])
continue;
/*倒水(改变状态)*/
int amount = min(cap[j], next.v[i] + next.v[j]) - next.v[j];
state s;
memcpy(&s, &next, sizeof(next));
s.dist = next.dist + amount;
s.v[i] -= amount;
s.v[j] += amount;
if (!vis[s.v[0]][s.v[1]])//水量一定,确定2个量,就能确定整个状态
{
vis[s.v[0]][s.v[1]] = 1;
pq.push(s);
}
}
}
}
while (d >= 0)
{
if (ans[d] >= 0)
{
printf("%d %d\n", ans[d], d);
return;
}
--d;
}
}
int main()
{
int n;
cin >> n;
while (n--)
{
cin >> a >> b >> c >> d;
cap[0] = a;
cap[1] = b;
cap[2] = c;
memset(vis, 0, sizeof(vis));
memset(ans, -1, sizeof(ans));
dijkstra();
}
return 0;
}