题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1317
题意:有N个房间,每个房间有一定的能量,当到达该房间,人的能量就是该房间的能量+原有的能量(房间的能量范围-100到100)。某些房间之间有单向的通道,问能否从房间1到房间N且中间过程中人的能量保持大于0。
分析:最长路+正环
1.首先可以发现,只要知道到第n个房间时最大可以获得多少能量值(当然必须保证中途都大于0),就能知道能否赢。
2.考虑到负环情况,跑SPFA的时候最长路负环是不会去循环的,所以负环其实可以不用考虑。
3.考虑到正环情况,只要能到达正环(即SPFA处理过程中接触到正环中的点),并且正环能到达终点,就一定能赢。
4.SPFA最长路松弛操作只要把松弛条件里的大于号小于号什么的反一下就可以了。
5.由于SPFA入队的点一定是能够由源点到达的点,而如果一个点进队次数大于等于n就说明它在环中(事实上由于最长路中负环不会循环所以这里一定是正环),那么这里只要能从该点到达终点(Floyd判断),那么就一定能赢;反之如果不能到达终点就一定会输(因为松弛次数大于或等于n还没有结束则说明最长路不存在)。
6.点权的写法,d数组的含义是由源点s到当前点u(包含u)的路径上的点权之和,所以初始化d[s]=100.
7.SPFA中松弛要求原图的连通情况,而不能用Floyd出来的结果。
8.注意松弛条件中本题要求中途都不能<=0因此松弛优化要满足优化值>0,于是要加个d[u]+E[v]>0。
9.注意最后判断一下到达第n号房间时的能量值是否>0,这个数值即为到达第n个房间时最大可以获得多少能量值。
10.程序中map数组用来存放原图,用来SPFA中的松弛条件;reach数组用来判断u->v的连通性。
11.注意Floyd是这样写的reach[i][j]=reach[i][j] || (reach[i][k] && reach[k][j]);
12.注意输入中可到达的房间编号是以1开始的,所以程序中想从0开始编号要将输入数值减1。
以上分析转自:http://blog.csdn.net/shoutmon/article/details/8582109
Source Code:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
const int maxv=105;
const int inf=-0x3f3f3f3f;
int E[maxv],num[maxv],dis[maxv];
bool map[maxv][maxv],reach[maxv][maxv];
int n,m,v;
void init(){
memset(num,0,sizeof(num));
memset(dis,inf,sizeof(dis));
memset(map,false,sizeof(map));
memset(reach,false,sizeof(reach));
}
void Folyd(){
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
reach[i][j]=reach[i][j]||(reach[i][k]&&reach[k][j]);
}
}
}
}
bool spfa(){
queue<int>Q;
Q.push(1);
dis[1]=100;
while(!Q.empty()){
int u=Q.front();
Q.pop();
if(++num[u]>=n) return reach[u][n];
for(int i=1;i<=n;i++){
if(map[u][i]&&dis[i]<dis[u]+E[i]&&dis[u]+E[i]>0){
dis[i]=dis[u]+E[i];
Q.push(i);
}
}
}
return dis[n]>0;
}
int main()
{
while(scanf("%d",&n),n+1){
init();
for(int i=1;i<=n;i++){
scanf("%d %d",&E[i],&m);
for(int j=0;j<m;j++){
scanf("%d",&v);
map[i][v]=true;
reach[i][v]=true;
}
}
Folyd();
if(!reach[1][n]) printf("hopeless\n");
else{
if(spfa()) printf("winnable\n");
else printf("hopeless\n");
}
}
return 0;
}