题目描述:
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数。
请你判断图中是否存在负权回路。
输入格式:
第一行包含整数 n 和 m。
接下来 m行每行包含三个整数 x,y,z表示存在一条从点 x 到点 y 的有向边,边长为 z。
输出格式:
如果图中存在负权回路,则输出 Yes
,否则输出 No
。
判断有没有负环,其实我们这里用一个数组存放到达每个点的边数,然后判断它的边数是否是大于等于顶点边数的,如果成立,那么就必然存在环,反之则没有。(因为n个顶点没有环的话,边最多就是n - 1)
#include <iostream>
#include <cstring>
#include <queue>
//需要用到队列,以及一个初始化函数:memset()
using namespace std;
const int N = 10010;
int n,m;
int e[N],h[N],ne[N],w[N],idx;
//邻接表的存储,有兴趣的可以去了解一下,w数组存储权值
int dis[N],cnt[N];
//dis数组存储到达点的距离,cnt数组存储到达每个点的边数
bool st[N];
//st数组用来标记队列里面的元素,如果在队列里面就为true,反之则为false
void add(int a,int b,int c)//邻接表的存储模板
{
e[idx] = b;
w[idx] = c;
ne[idx] = h[a];
h[a] = idx ++;
}
bool spfa()//这也是一个spfa的模板
{
queue<int> q;//声明一个队列
for(int i = 1; i <= n; i ++ )
{
st[i] = true;
q.push(i);
//将每个元素都存入队列,并且标记
}
while (q.size())//只要队列不为空,就继续执行
{
int t = q.front();//取队头元素
q.pop();//记得出队
st[t] = false;//出队过后,该元素就不在队列之中了,记得取消标记
for(int i = h[t]; i != -1; i = ne[i] )
{
int j = e[i];
if(dis[j] > dis[t] + w[i])
{
dis[j] = dis[t] + w[i];
cnt[j] = cnt[t] + 1;//记得边数加1
if(cnt[j] >= n) return true;
//如果边数大于等于n,则说明存在环,返回true即可(为什么返回这个,你看下面的输出
//判断条件就懂了)
if(!st[j])
{
st[j] = true;
q.push(j);
}
}
}
}
return false;//返回这个就说明不存在环
}
int main()
{
scanf("%d%d",&n,&m);
memset(h,-1,sizeof(h));//记得初始化h数组
while (m --)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
//存图
}
if(spfa()) printf("Yes\n");//返回true说明有环,输出Yes
else printf("No\n");//否则无环输出No
return 0;
}