【题目链接】
http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=19527
【解题报告】
lrj紫书中路径寻找问题的例题。大部分细节书中都有说明,不再赘述。说一点自己的感想。
书里面说算法的正确性不是显然的,可是我觉得应该是显然的吧?
每次找到队列里dist值最小的状态,以它来更新ans值,并且拓展其他状态。
假如当前我们找到了一个恰好有某个瓶子里的水为d升的状态p,现在我们把它push进队列。有两种情况:
1.p在队列首,说明它是dist值最小的状态,可以直接取出,然后结束搜索。
2.p在队列中。那么p后面的所有状态都可以砍去,因为无论如何都不会比
p状态更优。p前面的节点会优先被拓展,那么拓展之后比p更优的状态还会在p前面。所以如果p不是最优解,p一定不会被取出,反之,我们取出的第一个满足某个v[i]=d的状态一定是最优解。
不知道这样想有没有漏洞。如果有的话还请不吝指出。
【参考代码】
/*
这个题可以学到几个技巧:
1.struct状态的整体赋值
2.如何标记vis值(这里挺巧妙的)
3.在能够得到的答案里,如何找到最接近d的值
4.在优先队列里,最好在取出节点的时候更新答案
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
int cap[3];
struct state
{
int v[3];
int dis;
bool operator < ( const state& a )const { return dis>a.dis; }
};
int ans[210];
int vis[ 210 ][210];
void update( const state& u )
{
for( int i=0; i<3; i++ )
{
int d=u.v[i];
if( ans[d]==-1 || ans[d]>u.dis ) ans[d]=u.dis;
}
}
void solve( int a, int b, int c, int d )
{
cap[0]=a; cap[1]=b; cap[2]=c;
memset( ans,-1, sizeof ans ); //保存能达到的水量值
memset( vis, 0, sizeof vis ); //标记已经访问过的状态
state p0;
p0.v[0]=0; p0.v[1]=0; p0.v[2]=c; p0.dis=0;
priority_queue<state>q;
q.push(p0);
vis[0][0]=1;
while( !q.empty() )
{
state now=q.top(); q.pop(); //每次取出dis值最小的状态
update( now ); //要在每次取出的时候更新,不能在推入的时候更新。即使推入的时候可以到达d但未必是最优解
if( ans[d]>0 ) break;
for( int i=0; i<3; i++ )
for( int j=0; j<3; j++ ) //从i向j倒水
{
if(i==j)continue;
state next;
memcpy( &next,&now,sizeof now );
int temp=min( now.v[i], cap[j]-now.v[j] ); //要么j倒满,要么i倒空
next.v[i]-=temp;
next.v[j]+=temp;
next.dis+=temp;
if( !vis[ next.v[0] ][ next.v[1] ] ) { vis[ next.v[0] ][ next.v[1] ]=1; q.push( next ); }
}
}
while( d>=0 )
{
if( ans[d]>=0 ){ printf( "%d %d\n", ans[d],d); return; }
d--;
}
}
int main()
{
int T; cin>>T;
while( T-- )
{
int a,b,c,d; scanf( "%d%d%d%d",&a,&b,&c,&d );
solve( a,b,c,d );
}
return 0;
}