题意:
一个图, 点权代表走到该点可获得的能量值. 可正可负. 一个人从1 号出发,带有100点能量. 问是否有一种方案可使人在能量值>0的时候走到n.
思路:
这个题首先要注意点权. 其实就是这点的所有入边的边权都等于这点的点权.
要找长路, 而非最短路. 但是可以借助最短路的算法SPFA求.
最短路的算法SFPA主要是 队列 + 松弛
松弛操作直接关系到我们运行算法的目的----求最短路
如果与该点相邻的下一个点到源的距离可以因为通过该点中转而缩短 ,则更新此下一个点到源的最短距离, 也就相当于选择了走 经过该点中转这条路.(有点dp的意思?)
如果更新成功, 则意味着刚刚被更新的这一点有可能继续更新它临接的点. (如果没有被更新的话, 那么与它邻接的点, 要么已经被更新过(靠近源的,已出队的), 要么在队列中(靠近源的,在队列中), 要么还尚未涉及(远离源的), 就算询问也不可能执行松弛操作)
而队列的优化(相对于Bellman-Ford)则是把整个松弛操作放入了BFS的框架中, 主要是使得询问顺序合理, 而这种询问顺序是通用的, 与"最短路"并不是特殊匹配的.因此在这道题中尽管是为了找正环 or 最长路, 也是放在了SFPA的框架下.
啰嗦结束~
#include <cstdio> #include <queue> #include <cstring> const int MAXN=110; using namespace std; int n; int weight[MAXN]; int power[MAXN]; int _count[MAXN];//记录某点的入队列次数 bool map[MAXN][MAXN]; bool reach[MAXN][MAXN];//floyd判断任何两点是否可达 void Floyd(){ 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(int now){///求最长路.和求最短路的核心完全不同.只是借助了SPFA对图广搜的流程,判断正环并且避免负环 queue<int>Q; Q.push(now); memset(power,0,sizeof(power)); memset(_count,0,sizeof(_count)); power[1]=100; while(!Q.empty()){ int now=Q.front(); Q.pop(); _count[now]++;///统计该点入队次数 if(_count[now]>=n)return reach[now][n];//如果某个点的次数超过n次,那么说明存在正环,此时只要判断这点到n点是否可达就行了 for(int next=1;next<=n;next++){ if(map[now][next]&&power[now]+weight[next]>power[next]){ ///第一次进入时,只要不死都可以进入.第二次进入该店则必有环,那么保证不进入负环或0环 Q.push(next); power[next]=power[now]+weight[next]; } } } return power[n]>0; } int main(){ while(~scanf("%d",&n)&&n!=-1){ memset(map,false,sizeof(map)); memset(reach,false,sizeof(reach)); for(int i=1;i<=n;i++){ int num; scanf("%d%d",&weight[i],&num); for(int j=1;j<=num;j++){ int k; scanf("%d",&k); map[i][k]=true;///邻接矩阵 reach[i][k]=true;///bool可达矩阵 } } Floyd(); if(!reach[1][n]){ printf("hopeless\n"); }else { if(SPFA(1)){ printf("winnable\n"); }else printf("hopeless\n"); } } return 0; }