倒水问题(uva,10603)

题意:

设三个没有刻度杯子的容量为a, b, c, 刚开始只有第三个杯子装满水,其他两个 杯子为空,问最少需要倒多少升水才能使其中一个杯子中有 d 升水,若无法做到恰好 d 升,就让某一个杯子的水是 d' 升,其中 d' < d 并且尽量接近d,

注意:由于杯子没有刻度,用杯子x给杯子y倒水时,要么把x倒空,要么把y倒满。。

分析:

该题可看成图的最短路问题,把三个杯子的状态(三元组)看成图中的节点,两个状态倒水量看成边,(边的大小不同,不能用bfs)然后用Dijkstra算法求解即可。。

代码实现:

借鉴了紫书上的漂亮代码(自己写了180多的代码,结果错的莫名其妙

由于水的总量是固定的,所以根据前两个杯子的水量就可以确定一个状态,vis数组开个二维就行了

as数组存三个杯子水量,dis表示从初始状态到达此时状态所需的倒水量

ans[s]表示其中一个杯子的水量为s时的所需的最小倒水量

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
struct State{
	ll as[3],dis=0;
	State(ll a,ll b,ll c){
		as[0]=a,as[1]=b,as[2]=c;
	}
	bool operator>(State s2) const{
		return dis>s2.dis;
	}
};
bool vis[250][250];
ll ans[250]; 
priority_queue<State,vector<State>,greater<State>>q;
ll cap[250];
void update_ans(const State &u){
	ll s;
	for(ll i=0;i<3;i++){
		s=u.as[i];
		if(ans[s]<0||u.dis<ans[s]) ans[s]=u.dis;
	}
}
void solve(ll a,ll b,ll c,ll d){
	while(!q.empty()){
		State u=q.top();q.pop();
		update_ans(u);
		if(vis[u.as[0]][u.as[1]]) continue;
		if(ans[d]>=0) return;
		vis[u.as[0]][u.as[1]]=1;
		for(ll i=0;i<3;i++){     //i是倒水的杯子,j是被倒的 
			for(ll j=0;j<3;j++){
				State tmp=u;
				if(i==j||u.as[i]==0||u.as[j]==cap[j]) continue;
				ll amount=min(cap[j],u.as[i]+u.as[j])-u.as[j]; //倒水量 ,j倒完后的水量-倒之前的水量 
				u.as[i]-=amount;
				u.as[j]+=amount;
				u.dis+=amount;
				if(!vis[u.as[0]][u.as[1]])
					q.push(u);
				u=tmp;
			}
		}
	}
}
int main()
{
	ll t,a,b,c,d;
	cin>>t;
	while(t--){
		while(!q.empty()) q.pop();
		memset(vis,0,sizeof(vis));
		memset(ans,-1,sizeof(ans));
		cin>>a>>b>>c>>d;
		cap[0]=a,cap[1]=b,cap[2]=c;
		q.push(State(0,0,c));
		solve(a,b,c,d);
		for(ll s=d;s>=0;s--){
			if(ans[s]>=0){
				cout<<ans[s]<<" "<<s<<endl;
				break;
			}
		}
	} 

	return 0;
}

ps:

第一次写博客,我尽力了...

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值