此题的特点在于它的解是一个向六个方向扩展的解答树,我们可以用dfs来做搜索,由于该解答树的扩展是无限的。必须做好回溯的判断标准。
对于如何写dfs的问题,我的看法是,找准某个节点的对应信息量,换句话讲,某个状态,三个瓶子里的牛奶量是进行下一次深入的参数。
所以dfs函数中要包含三个参数,进行六次递归。
在时间问题上,dfs往往比较耗时,可行的提速方法是利用队列或栈的高速缓存,或者是避免状态重复的动态规划。
dfs在写的时候尽量把操作写成函数,避免递归带来的空间爆炸。
标程dfs:
typedef struct State State;
struct State {
int a[3];
};
State//操作函数独立编写
pour(State s, int from, int to)
{
int amt;
amt = s.a[from];
if(s.a[to]+amt > cap[to])
amt = cap[to] - s.a[to];
s.a[from] -= amt;
s.a[to] += amt;
return s;
}
void
search(State s)
{
int i, j;
if(seen[s.a[0]][s.a[1]][s.a[2]])//回溯判定
return;
seen[s.a[0]][s.a[1]][s.a[2]] = 1;
if(s.a[0] == 0) //解答的回溯判定
canget[s.a[2]] = 1;
for(i=0; i<3; i++)
for(j=0; j<3; j++)
search(pour(s, i, j)); //6次递归
}
dp的代码
dp的实现得益于状态的存在本身就是解答,没有相互影响得问题。(部分标程如下)
for(flag=1;flag;)
{
flag=0;//是否遍历了所有的状态
for(i=0;i<=A;i++)
for(j=0;j<=B;j++)
for(k=0;k<=C;k++) {
if(m[i][j][k]) {
if(i==0) poss[k]=1;
if(i) {//以下的3个分支就是模拟倒牛奶的操作,有重复状态flag不会置1
if(j<B) {
if(B-j>=i) {
if( m[0][j+i][k]==0) {
m[0][j+i][k]=1;
flag=1;
}
} else {
if( m[i-(B-j)][B][k] == 0) {
m[i-(B-j)][B][k] =1;
flag=1;
}
}
}
if(k<C) {
if(C-k>=i) {
if( m[0][j][k+i]==0) {
m[0][j][k+i]=1;
flag=1;
}
}
else {
if( m[i-(C-k)][j][C] == 0) {
m[i-(C-k)][j][C] =1;
flag=1;
}
}
}
}
if(j) {
if(i<A) {
if(A-i>=j) {
if( m[i+j][0][k]==0) {
m[i+j][0][k]=1;
flag=1;
}
} else {
if( m[A][j-(A-i)][k] == 0) {
m[A][j-(A-i)][k] =1;
flag=1;
}
}
}
if(k<C) {
if(C-k>=j) {
if( m[i][0][k+j]==0) {
m[i][0][k+j]=1;
flag=1;
}
} else {
if( m[i][j-(C-k)][C] == 0) {
m[i][j-(C-k)][C] =1;
flag=1;
}
}
}
}
if(k) {
if(i<A) {
if(A-i>=k) {
if( m[i+k][j][0]==0) {
m[i+k][j][0]=1;
flag=1;
}
} else {
if( m[A][j][k-(A-i)] == 0) {
m[A][j][k-(A-i)] =1;
flag=1;
}
}
}
if(j<B) {
if(B-j>=k) {
if( m[i][j+k][0]==0) {
m[i][j+k][0]=1;
flag=1;
}
} else {
if( m[i][B][k-(B-j)] == 0) {
m[i][B][k-(B-j)] =1;
flag=1;
}
}
}
}
}
}
}