题目:http://acm.hdu.edu.cn/showproblem.php?pid=4784
题目大意:现在给你N个点,M条有向边,你从点1出发,要到达点N。每条边上有需要花费的时间和钱,你出门时带着R块钱,每个点还可以买一包盐,卖一包盐或者什么都不做,手上最多能拿B包盐,而且你手上有一个机器,能花费1分钟而且不花钱带你从k时空穿越到下一个平行时空 (k+1)%K,平行时空里的点和边和最初空间(0空间)的一样,只是这些点盐的价格会不同。你要在不超过T的时间内从1到达N,并且使钱最大。在途中钱不能为负,点1和点N只能在0空间的时候访问,而且一到点N就马上停了。
解题思路:
这一看,就想到DP嘛,而且状态和转换都比较简单,这一算时间复杂度,好像也行,就写了个记忆化,果然不出意料,TLE了,试着用最短路优化剪剪枝,还是TLE。。
后来看了人家解题报告,原来要根据时间,用优先队列优化。话说,这一招,以前真没碰到过,好好的学了下,果然是diao啊!如果是dp普通的写法,直接for循环递推或者spfa都行,for写的话,所有状态都罗列了,可能还是spfa比较快一点,但写起来快,代码短。这题如果用 spfa 的话,也是TLE。spfa 的时间复杂度是O(kE),K(K<<V,貌似一般2左右)为点进入队列几次,一般情况下,稀疏图很好,稠密图的话,需要考虑考虑。对于这题,还是TLE,肿么办?想想,能不能让每个点只进入队列一次。
由于每个状态往后时间都是增加的,如果用按目前为止到达当前状态所花费的时间递增排序,一旦某个状态从队列里出来,那么它就不可能再次进入队列,其他队列里的状态时间都比它大呀,不可能来更新!这样就可以做到每个状态只进入队列一次,时间复杂度O(VlogV+E),V = N*B*K*T,E = B*K*T*M。
dp[ pos ][ b ][ k ][ t ],表示在pos点,手上有b包盐,当前在k维空间,已用时t分钟能够获得的最多的钱。另外要注意,一到 N 点就停,所以如果碰到 N 点出来,要continue,否则 WA,因为,试想,某个点只能从 N 点绕过去,并且收益很高,那么如果不处理,肯定过走卖,但这是不行的!
代码如下:
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
const int MAXN = 111;
int N,M,B,K,R,T;
struct Edge
{
int t,time,fee;
Edge(int _t,int _time,int _fee)
{
t = _t,time = _time,fee = _fee;
}
};
vector <Edge> G[MAXN];
int dp[MAXN][11][11][222];
int pri[11][MAXN];
struct Node
{
int pos,b,k,time;
Node(int _pos,int _b,int _k,int _time)
{
pos = _pos,b = _b;
k = _k,time = _time;
}
bool operator < (const Node &tmp) const
{
return time > tmp.time;
}
};
bool vis[MAXN][11][11][222];
void DP()
{
memset(vis,0,sizeof(vis));
memset(dp,-1,sizeof(dp));
priority_queue <Node> q;
dp[0][0][0][0] = R;
q.push(Node(0,0,0,0));
while(!q.empty())
{
Node cur = q.top();q.pop();
if(cur.pos == N-1) continue;//注意,一定要加,否则WA
for(int i = 0;i < G[cur.pos].size();i++)
{
int to = G[cur.pos][i].t;
int time = G[cur.pos][i].time;
int fee = G[cur.pos][i].fee;
for(int salt = -1;salt <= 1;salt++)
{
if(cur.b+salt < 0 || cur.b+salt > B) continue;
if(pri[cur.k][to] == -1 && salt != 0) continue;
if(cur.time+time > T) continue;
if((to == 0 || to == N-1) && cur.k != 0) continue;
int tmp = dp[cur.pos][cur.b][cur.k][cur.time]-salt*pri[cur.k][to]-fee;
if(tmp < 0) continue;
if(dp[to][cur.b+salt][cur.k][cur.time+time] < tmp)
{
dp[to][cur.b+salt][cur.k][cur.time+time] = tmp;
if(!vis[to][cur.b+salt][cur.k][cur.time+time])
{
q.push(Node(to,cur.b+salt,cur.k,cur.time+time));
vis[to][cur.b+salt][cur.k][cur.time+time] = 1;
}
}
}
}
for(int salt = -1;salt <= 1;salt++)
{
if(cur.b+salt < 0 || cur.b+salt > B) continue;
if(pri[(cur.k+1)%K][cur.pos] == -1 && salt != 0) continue;
if(cur.time+1 > T) continue;
if(cur.pos == 0 || cur.pos == N-1) continue;
int tmp = dp[cur.pos][cur.b][cur.k][cur.time]-salt*pri[(cur.k+1)%K][cur.pos];
if(tmp < 0) continue;
if(dp[cur.pos][cur.b+salt][(cur.k+1)%K][cur.time+1] < tmp)
{
dp[cur.pos][cur.b+salt][(cur.k+1)%K][cur.time+1] = tmp;
if(!vis[cur.pos][cur.b+salt][(cur.k+1)%K][cur.time+1])
{
q.push(Node(cur.pos,cur.b+salt,(cur.k+1)%K,cur.time+1));
vis[cur.pos][cur.b+salt][(cur.k+1)%K][cur.time+1] = 1;
}
}
}
}
}
int main()
{
int cas = 0;
int _;
scanf("%d",&_);
while(_--)
{
scanf("%d%d%d%d%d%d",&N,&M,&B,&K,&R,&T);
for(int i = 0;i < K;i++)
for(int j = 0;j < N;j++)
scanf("%d",&pri[i][j]);
for(int i = 0;i < N;i++) G[i].clear();
for(int i = 0;i < M;i++)
{
int a,b,c,d;
scanf("%d%d%d%d",&a,&b,&c,&d);
a--,b--;
G[a].push_back(Edge(b,c,d));
}
DP();
int ans = -1;
for(int i = 0;i <= T;i++)
ans = max(ans,dp[N-1][0][0][i]);
printf("Case #%d: ",++cas);
if(ans < 0) puts("Forever Alone");
else printf("%d\n",ans);
}
return 0;
}
/*
2
3 2 1 2 10 6
-1 1 -1
-1 5 -1
1 2 1 0
2 3 1 1
2 2 1 2 5 5
-1 -1
-1 -1
1 2 10 2
1 2 2 10
*/