问题描述
蒜头君在玩一个很好玩的游戏,这个游戏一共有至多 100 个地图,其中地图 1 是起点,房间 n 是终点。有的地图是补给站,可以加 ki点体力,而有的地图里存在怪物,需要消耗 ki 点体力,地图与地图之间存在一些单向通道链接。
蒜头君从 1 号地图出发,有 100 点初始体力。每进入一个地图的时候,需要扣除或者增加相应的体力值。这个过程持续到走到终点,或者体力值归零就会 Game Over。不过,他可以经过同个地图任意次,且每次都需要接受该地图的体力值。
输入格式
第 1 行一个整数 n (n≤100)。
第 2 ~ n+1 行,每行第一个整数表示该地图体力值变化。接下来是从该房间能到达的房间名单,第一个整数表示房间数,后面是能到达的房间编号。
输出格式
若玩家能到达终点,输出Yes,否则输出No。
样例输入
5
0 1 2
-60 1 3
-60 1 4
20 1 5
0 0
样例输出
No
!!!!注意:
原题题意太蠢了,意思是第二行代表结点1,可以到达房间数为1,即房间2,消耗0生命值,依次类推
我改成
第 1 行两个个整数 n (n≤100) , m
下面m行,每行第一个整数表示起始房间号。接下来是终点房间号,最后一个整数为负数表示消耗生命值,正数为加生命值改编后输入样例
7 7
1 2 20
2 5 -1000
2 3 30
3 4 40
4 2 20
5 6 -1000
6 7 -1000输出样例
Yes
思路:将生命值变化转换成边的权重,而结点1到自身,即dis[1] = 100,然后找单源最长路,注意若图中有正环,需要特殊处理,在代码中有给出
#include <bits/stdc++.h>
using namespace std;
const int INF = 10000000;
const int N = 105;
const int M = N*N+10;
struct edge{
int v,w,next;
}edges[M];
int head[N];
int nums = 0;
void insert_list(int u,int v,int w){ //链表头插法
edges[nums].v = v;
edges[nums].w = w;
edges[nums].next = head[u];
head[u] = nums++;
}
queue<int> q;
bool vis[N]; //判断是否已经在队列中
int cnt[N]; //存储入队次数
int dis[N]; //源点到每个点的距离
int n;
//求最长路
bool SPFA(int u){
memset(dis,-0x3f,sizeof dis); //求最长路
dis[u] = 100;
q.push(u);
vis[u] = true;
cnt[u]++;
while(!q.empty()){
int curr_v = q.front();
q.pop();
vis[curr_v] = false;
if(dis[n] == INF){ //若终点在正环上或者经过正环能到终点,即刻退出
return true;
}
if(cnt[curr_v] > n){ //若存在正环,此结点不再入队,否则会出现死循环
vis[curr_v] = true;
}
for(int j = head[curr_v]; ~j; j = edges[j].next){
int v = edges[j].v;
int w = edges[j].w;
if(dis[v] < dis[curr_v] + w){ //求最长路
dis[v] = dis[curr_v] + w;
if(!vis[v]){
q.push(v);
vis[v] = true;
cnt[v]++;
/*当这个结点入队次数超过结点数n,说明存在正环,正环代表你可以一直在这刷生命值,
若从起点到终点经过这个环,则必能通关,让这个结点的生命值开到正无限即可*/
if(cnt[v] > n)
{
dis[v] = INF;
}
}
}
}
}
if(dis[n] > 0){
return true;
}
else{
return false;
}
}
int main() {
int m;
cin>>n>>m;
memset(head,-1,sizeof head); //初始化头节点
for(int i = 1; i <= m; ++i){
int hp,rooms,to;
cin>>rooms>>to>>hp;
insert_list(rooms,to,hp);
}
if(SPFA(1)){
cout<<"Yes"<<endl;
}
else{
cout<<"No"<<endl;
}
return 0;
}