端午回家嗨之前,刷道题提前嗨~
Problem Description
大家一定觉的运动以后喝可乐是一件很惬意的事情,但是seeyou却不这么认为。因为每次当seeyou买了可乐以后,阿牛就要求和seeyou一起分享这一瓶可乐,而且一定要喝的和seeyou一样多。但seeyou的手中只有两个杯子,它们的容量分别是N 毫升和M 毫升 可乐的体积为S (S<101)毫升 (正好装满一瓶) ,它们三个之间可以相互倒可乐 (都是没有刻度的,且 S==N+M,101>S>0,N>0,M>0) 。聪明的ACMER你们说他们能平分吗?如果能请输出倒可乐的最少的次数,如果不能输出"NO"。Input
三个整数 : S 可乐的体积 , N 和 M是两个杯子的容量,以"0 0 0"结束。
Output
如果能平分的话请输出最少要倒的次数,否则输出"NO"。
Sample Input
7 4 3
4 1 3
0 0 0
Sample Output
NO
3
思路:bfs
定义状态:{a,b,c,t},分别表示容积为s的瓶子里的可乐体积a、容积为n的瓶子里的可乐体积b,容积为m的瓶子里面的可乐体积c。t则表示从初始状态{s,0,0,0}到达当前状态{a,b,c,t}的最小次数。
很容易看出,当s为奇数时是不可能平分可乐的。
而重复到达的状态是无效的,可以根据这个给dfs剪枝,当状态中的a,b,c任意两个为s/2时,说明存在最小要倒次数。当遍历完整棵状态树时都没有找到平均状态,说明状态树中不存在平均状态。
实现代码:
import java.io.*;
import java.util.*;
public class Main {
//定义状态
static class DP{
int a; //s中可乐的体积
int b; //n中可乐的体积
int c; //m中可乐的体积
int t; //记录达到该状态时 所用最少的次数
public DP(int a,int b,int c,int t) {
this.a = a;
this.b = b;
this.c = c;
this.t = t;
}
}
//非常可乐
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StreamTokenizer cin = new StreamTokenizer(br);
PrintWriter pr = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
while(true) {
cin.nextToken();
int s = (int) cin.nval;
cin.nextToken();
int n = (int) cin.nval;
cin.nextToken();
int m = (int) cin.nval;
if(m==0&&n==0&&s==0)
break;
Queue<DP> queue = new LinkedList<DP>();
//定义状态标记 重复到达某状态是无效的
int[][][] mark = new int[s+1][s+1][s+1]; //可乐的体积是小于101的
if((s&1)==1) {
//奇数是不可能平分的
pr.println("NO");
continue;
}
//将第一个状态放进队列
DP dp = new DP(s,0,0,0);
mark[s][0][0] = 1;
queue.add(dp);
int res = bfs(queue,mark,s,n,m);
//如果遍历完所有的状态 都没找到一个平均值的话 就返回false
if(res == -1)
pr.println("NO");
else
pr.println(res);
}
pr.flush();
}
private static int bfs(Queue<DP> queue, int[][][] mark, int s,int n,int m) {
while(queue.isEmpty()==false) {
DP dp = queue.poll();
int[] res;
DP newdp;
//s->n
res = poll(s,dp.a,n,dp.b);
newdp = new DP(res[0],res[1],dp.c,dp.t+1);
//如果这个新状态是重复状态 就忽略它
//只有当新状态不是重复状态时 才将它入队列 并标记已经判断过该状态
if(mark[newdp.a][newdp.b][newdp.c] != 1) {
if(newdp.a==s/2&&newdp.b==s/2)
return newdp.t;
if(newdp.a==s/2&&newdp.c==s/2)
return newdp.t;
if(newdp.b==s/2&&newdp.c==s/2)
return newdp.t;
mark[newdp.a][newdp.b][newdp.c] = 1;
//将该状态入队列 以访问其儿子
queue.add(newdp);
}
//n->s
res = poll(n,dp.b,s,dp.a);
newdp = new DP(res[1],res[0],dp.c,dp.t+1);
//如果这个新状态是重复状态 就忽略它
//只有当新状态不是重复状态时 才将它入队列 并标记已经判断过该状态
if(mark[newdp.a][newdp.b][newdp.c] != 1) {
if(newdp.a==s/2&&newdp.b==s/2)
return newdp.t;
if(newdp.a==s/2&&newdp.c==s/2)
return newdp.t;
if(newdp.b==s/2&&newdp.c==s/2)
return newdp.t;
mark[newdp.a][newdp.b][newdp.c] = 1;
//将该状态入队列 以访问其儿子
queue.add(newdp);
}
//s->m
res = poll(s,dp.a,m,dp.c);
newdp = new DP(res[0],dp.b,res[1],dp.t+1);
//如果这个新状态是重复状态 就忽略它
//只有当新状态不是重复状态时 才将它入队列 并标记已经判断过该状态
if(mark[newdp.a][newdp.b][newdp.c] != 1) {
if(newdp.a==s/2&&newdp.b==s/2)
return newdp.t;
if(newdp.a==s/2&&newdp.c==s/2)
return newdp.t;
if(newdp.b==s/2&&newdp.c==s/2)
return newdp.t;
mark[newdp.a][newdp.b][newdp.c] = 1;
//将该状态入队列 以访问其儿子
queue.add(newdp);
}
//m->s
res = poll(m,dp.c,s,dp.a);
newdp = new DP(res[1],dp.b,res[0],dp.t+1);
//如果这个新状态是重复状态 就忽略它
//只有当新状态不是重复状态时 才将它入队列 并标记已经判断过该状态
if(mark[newdp.a][newdp.b][newdp.c] != 1) {
if(newdp.a==s/2&&newdp.b==s/2)
return newdp.t;
if(newdp.a==s/2&&newdp.c==s/2)
return newdp.t;
if(newdp.b==s/2&&newdp.c==s/2)
return newdp.t;
mark[newdp.a][newdp.b][newdp.c] = 1;
//将该状态入队列 以访问其儿子
queue.add(newdp);
}
//n->m
res = poll(n,dp.b,m,dp.c);
newdp = new DP(dp.a,res[0],res[1],dp.t+1);
//如果这个新状态是重复状态 就忽略它
//只有当新状态不是重复状态时 才将它入队列 并标记已经判断过该状态
if(mark[newdp.a][newdp.b][newdp.c] != 1) {
if(newdp.a==s/2&&newdp.b==s/2)
return newdp.t;
if(newdp.a==s/2&&newdp.c==s/2)
return newdp.t;
if(newdp.b==s/2&&newdp.c==s/2)
return newdp.t;
mark[newdp.a][newdp.b][newdp.c] = 1;
//将该状态入队列 以访问其儿子
queue.add(newdp);
}
//m->n
res = poll(m,dp.c,n,dp.b);
newdp = new DP(dp.a,res[1],res[0],dp.t+1);
//如果这个新状态是重复状态 就忽略它
//只有当新状态不是重复状态时 才将它入队列 并标记已经判断过该状态
if(mark[newdp.a][newdp.b][newdp.c] != 1) {
if(newdp.a==s/2&&newdp.b==s/2)
return newdp.t;
if(newdp.a==s/2&&newdp.c==s/2)
return newdp.t;
if(newdp.b==s/2&&newdp.c==s/2)
return newdp.t;
mark[newdp.a][newdp.b][newdp.c] = 1;
//将该状态入队列 以访问其儿子
queue.add(newdp);
}
}
return -1;
}
//将容器s中的aL可乐 倒入 容器m中 已知容器m中有bL可乐
//返回 容器s 中的剩余可乐 和容器m 中的现有可乐
private static int[] poll(int s, int a, int m, int b) {
int[] res = new int[2];
int rest = m-b; //后者能够装可乐的空间
//如果后者能装下前者全部可乐
if(rest>=a) {
//将前者的可乐全部倒入后者
res[0] = 0;
res[1] = a+b;
return res;
}
//后者只能装下前者的 rest 容积的可乐
res[0] = a-rest;
res[1] = m;
return res;
}
}
想上手试试?点我!