zl程序教程

您现在的位置是:首页 >  其它

当前栏目

倒水问题(dp+bfs)

DP BFS 问题
2023-09-11 14:20:52 时间

1 问题描述

有A、B、C三个杯子,没有刻度,容量分别为A、B、C,只使用这三个杯子,如何量出容量为K的水?

要求:水可以无限用,每个杯子的水可以倒掉,也可以重新装满,还可以倒进别的杯子。

2 状态分析

以每个杯子的水的盛水量为状态,即状态节点使用三元组(a,b,c)表示,一共有(A+1)*(B+1)*(C+1)种状态。若由T1状态经一步倒水操作可达到T2状态,则T1到T2间有一条边,记为T1->T2,由此可建立一个有向图。

对于每个状态,最多有如下12条状态转移规则:

  • 若a>0  ==>  a=0;(若A杯有水,倒掉A杯水)
  • 若b>0  ==>  b=0;(若B杯有水,倒掉B杯水)
  • 若c>0  ==>  c=0;(若C杯有水,倒掉C杯水)
  • 若a<A  ==>  a=A;(若A杯未满,倒满A杯水)
  • 若b<B  ==>  b=B;(若B杯未满,倒满B杯水)
  • 若c<C  ==>  c=C;(若C杯未满,倒满C杯水)
  • 若a<A && b>0  ==>  若b+a<=A,a=b+a, b=0 ;若b+a>A,b=b+a-A, a=A;(B->A)
  • 若a<A && c>0  ==>  若c+a<=A,a=c+a, c=0 ;若c+a>A,c=c+a-A, a=A;(C->A)
  • 若b<B && a>0  ==>  若a+b<=B,b=a+b, a=0 ;若a+b>B,a=a+b-B, b=B;(A->B)
  • 若b<B && c>0  ==>  若c+b<=B,b=c+b, c=0 ;若c+b>B,c=c+b-B, b=B;(C->B)
  • 若c<C && a>0  ==>  若a+c<=C,c=a+c, a=0;若a+c>C,a=a+c-C, c=C;(A->C)
  • 若c<C && b>0  ==>  若b+c<=C,c=b+c, b=0;若b+c>C,b=b+c-C, c=C;(B->C)

3 算法分析

定义数组 dp[A+1][B+1][C+1] ,dp[i][j][k] 表示从状态(0, 0, 0)到状态(i, j, k)所需经过的步数,定义数组path[A+1][B+1][C+1] 表示从状态(0, 0, 0)到状态(i, j, k)在步数最少的情况下,前一状态信息。

使用广度优先搜索(BFS),每次状态步数有变动时,更新步数,并把当前状态添加到队列中,以便下次更新当前状态的下一个状态的步数。

核心代码:

import java.util.LinkedList;
import java.util.Scanner;
import java.util.Stack;

class Status{
	int a;
	int b;
	int c;
	Status(int a,int b,int c){
		this.a=a;
		this.b=b;
		this.c=c;
	}
}
public class Main {
	static int A,B,C,K;
	static int[][][] dp;
	static int ans;
	static final int MAX=10000000;
	static Status[][][] path;
	static LinkedList<Status> que=new LinkedList<Status>();
	
	public static void main(String[] args) {
		Scanner sc=new Scanner(System.in);
		A=sc.nextInt();
		B=sc.nextInt();
		C=sc.nextInt();
		K=sc.nextInt();
		dp=new int[A+1][B+1][C+1];
		path=new Status[A+1][B+1][C+1];
		for(int i=0;i<=A;i++) {
			for(int j=0;j<=B;j++) {
				for(int k=0;k<=C;k++) {
					dp[i][j][k]=MAX;
					path[i][j][k]=new Status(0,0,0);
				}
			}
		}
		bfs(); //使用BFS填充dp表
		Status status=choose(); //选择有一个杯子盛水量为K,且步数最少的状态
		print(status); //打印路径及最小步数
	}
	
	static void bfs() { //使用BFS填充dp表
		dp[0][0][0]=0;
		path[0][0][0]=null;
		que.add(new Status(0,0,0));
		while(!que.isEmpty()) {
			Status sta=que.remove(); //出队
			int a=sta.a;
			int b=sta.b;
			int c=sta.c;
			for(int i=0;i<12;i++) { //遍历12个孩子状态
				Status s=change(a,b,c,i); //改变盛水量(从当前状态到下一个状态)
				if(s!=null) {
					que.add(s); //入队
				}
			}
		}
	}
}

运行结果:

其他方法代码 :

static Status choose() { //选择有一个杯子盛水量为K,且步数最少的状态
	int a=0,b=0,c=0;
	ans=MAX;
	if(K<=C) {
		for(int i=0;i<=A;i++) {
			for(int j=0;j<=B;j++) {
				if(dp[i][j][K]<ans) {
					ans=dp[i][j][K];
					a=i;
					b=j;
					c=K;
				}
			}
		}
	}
	if(K<=B) {
		for(int i=0;i<=A;i++) {
			for(int k=0;k<=C;k++) {
				if(dp[i][K][k]<ans) {
					ans=dp[i][K][k];
					a=i;
					b=K;
					c=k;
				}
			}
		}
	}
	if(K<=A) {
		for(int j=0;j<=B;j++) {
			for(int k=0;k<=C;k++) {
				if(dp[K][j][k]<ans) {
					ans=dp[K][j][k];
					a=K;
					b=j;
					c=k;
				}
			}
		}
	}
	return new Status(a,b,c);
}

static void print(Status status) { //打印路径及最小步数
	Stack<Status> stack=new Stack<Status>();
	while(status!=null) {
		stack.add(status);
		int a=status.a;
		int b=status.b;
		int c=status.c;
		status=path[a][b][c];
	}
	while(!stack.isEmpty()) {
		status=stack.pop();
		int a=status.a;
		int b=status.b;
		int c=status.c;
		if(stack.size()>0)
			System.out.print("("+a+","+b+","+c+")->");
		else
			System.out.print("("+a+","+b+","+c+")");
	}
	System.out.println("\n"+ans);
}

static Status change(int i,int j,int k,int s) { //改变盛水量(从当前状态到下一个状态)
	Status sta=new Status(i,j,k);
	switch(s) {
		case 0: //倒空A杯水
			if(i>0) {
				if(dp[0][j][k]>dp[i][j][k]+1) {
					dp[0][j][k]=dp[i][j][k]+1;
					path[0][j][k]=sta;
					return new Status(0,j,k);
				}
			}
			return null;
		case 1: //倒空B杯水
			if(j>0) {
				if(dp[i][0][k]>dp[i][j][k]+1) {
					dp[i][0][k]=dp[i][j][k]+1;
					path[i][0][k]=sta;
					return new Status(i,0,k);
				}
			}
			return null;
		case 2:  //倒空C杯水
			if(k>0) {
				if(dp[i][j][0]>dp[i][j][k]+1) {
					dp[i][j][0]=dp[i][j][k]+1;
					path[i][j][0]=sta;
					return new Status(i,j,0);
				}
			}
			return null;
		case 3:  //倒满A杯水
			if(i<A) {
				if(dp[A][j][k]>dp[i][j][k]+1) {
					dp[A][j][k]=dp[i][j][k]+1;
					path[A][j][k]=sta;
					return new Status(A,j,k);
				}
			}
			return null;
		case 4:  //倒满B杯水
			if(j<B) {
				if(dp[i][B][k]>dp[i][j][k]+1) {
					dp[i][B][k]=dp[i][j][k]+1;
					path[i][B][k]=sta;
					return new Status(i,B,k);
				}
			}
			return null;
		case 5: //倒满C杯水
			if(k<C) {
				if(dp[i][j][C]>dp[i][j][k]+1) {
					dp[i][j][C]=dp[i][j][k]+1;
					path[i][j][C]=sta;
					return new Status(i,j,C);
				}
			}
			return null;
		case 6: //B->A
			if(j>0&&i<A) {
				if(i+j<=A) {
					if(dp[i+j][0][k]>dp[i][j][k]+1) {
						dp[i+j][0][k]=dp[i][j][k]+1;
						path[i+j][0][k]=sta;
						return new Status(i+j,0,k);
					}
				}else {
					if(dp[A][i+j-A][k]>dp[i][j][k]+1) {
						dp[A][i+j-A][k]=dp[i][j][k]+1;
						path[A][i+j-A][k]=sta;
						return new Status(A,i+j-A,k);
					}
				}
			}
			return null;
		case 7: //C->A
			if(k>0&&i<A) {
				if(i+k<=A) {
					if(dp[i+k][j][0]>dp[i][j][k]+1) {
						dp[i+k][j][0]=dp[i][j][k]+1;
						path[i+k][j][0]=sta;
						return new Status(i+k,j,0);
					}
				}else {
					if(dp[A][j][i+k-A]>dp[i][j][k]+1) {
						dp[A][j][i+k-A]=dp[i][j][k]+1;
						path[A][j][i+k-A]=sta;
						return new Status(A,j,i+k-A);
					}
				}
			}
			return null;
		case 8: //A->B
			if(i>0&&j<B) {
				if(i+j<=B) {
					if(dp[0][i+j][k]>dp[i][j][k]+1) {
						dp[0][i+j][k]=dp[i][j][k]+1;
						path[0][i+j][k]=sta;
						return new Status(0,i+j,k);
					}
				}else {
					if(dp[i+j-B][B][k]>dp[i][j][k]+1) {
						dp[i+j-B][B][k]=dp[i][j][k]+1;
						path[i+j-B][B][k]=sta;
						return new Status(i+j-B,B,k);
					}
				}
			}
			return null;
		case 9: //C-B
			if(k>0&&j<B) {
				if(j+k<=B) {
					if(dp[i][j+k][0]>dp[i][j][k]+1) {
						dp[i][j+k][0]=dp[i][j][k]+1;
						path[i][j+k][0]=sta;
						return new Status(i,j+k,0);
					}
				}else {
					if(dp[i][B][j+k-B]>dp[i][j][k]+1) {
						dp[i][B][j+k-B]=dp[i][j][k]+1;
						path[i][B][j+k-B]=sta;
						return new Status(i,B,j+k-B);
					}
				}
			}
			return null;
		case 10: //A->C
			if(i>0&&k<C) {
				if(i+k<=C) {
					if(dp[0][j][i+k]>dp[i][j][k]+1) {
						dp[0][j][i+k]=dp[i][j][k]+1;
						path[0][j][i+k]=sta;
						return new Status(0,j,i+k);
					}
				}else {
					if(dp[i+k-C][j][C]>dp[i][j][k]+1) {
						dp[i+k-C][j][C]=dp[i][j][k]+1;
						path[i+k-C][j][C]=sta;
						return new Status(i+k-C,j,C);
					}
				}
			}
			return null;
		case 11: //B->C
			if(j>0&&k<C) {
				if(j+k<=C) {
					if(dp[i][0][j+k]>dp[i][j][k]+1) {
						dp[i][0][j+k]=dp[i][j][k]+1;
						path[i][0][j+k]=sta;
						return new Status(i,0,j+k);
					}
				}else {
					if(dp[i][j+k-C][C]>dp[i][j][k]+1) {
						dp[i][j+k-C][C]=dp[i][j][k]+1;
						path[i][j+k-C][C]=sta;
						return new Status(i,j+k-C,C);
					}
				}
			}
			return null;
	}
	return null;
}