本博客主要介绍spfa求负环
一般用第二种方法
第一种方法如果每个点入队n次,每次入队也要遍历n次,那么时间复杂度就是n2
第二种方法时间复杂度是n,只要发现最短路边数>=n就说明有环了
#include
#include
const int N = 510, M =2 * 2500 + 200 + 10;
using namespace std;
int dist[N];
int q[N], cnt[N];
bool st[N];
int h[N], ne[M], e[M], w[M], idx;
int T, n, m1, m2;
void add (int a, int b, int c)
{
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx ++ ;
}
bool spfa()
{
//memset(dist, 0x3f, sizeof dist);//0x3f也可以ac啊,是因为有负环因为要判断的是负环,
//如果存在负环,那么肯定存在一个点到虚拟源点的距离是负无穷,
//相比于负无穷,0和正无穷都是 一个很大的值,因此这个初始化可以用来更新
memset(dist, 0, sizeof dist);//这里dist初始化不是0x3f而是0
memset(cnt, 0, sizeof cnt);
memset(st, 0, sizeof st);
//虚拟源点求负环
int hh = 0, tt = 0;
for (int i = 1;i <= n; i ++ )
{
q[tt ++ ] = i;
st[i] = true;
}
while (hh != tt)
{
int t = q[hh ++ ];
if (hh == N) hh = 0;
st[t] = false;
for (int i = h[t]; ~i; i = ne[i])
{
int j = e[i]; //现在农夫约翰希望能够从农场中的某片田地出发,经过一些路径和虫洞回到过去,并在他的出发时刻之前赶到他的出发地。
if (dist[j] > dist[t] + w[i])//这里是求负环,负环对应最短路,因为走负环可以让代价减少所有求的是最短路
{ //而正环改变一下符号就行,正环对应最长路,因为走正环可以让代价增加
dist[j] = dist[t] + w[i];
cnt[j] = cnt[t] + 1;
if (cnt[j] >= n) return true;
if (st[j] == false)
{
q[tt ++ ] = j;
if (tt == N) tt = 0;
st[j] = true;
}
}
}
}
return false;
}
int main()
{
cin >> T;
while (T -- )
{
memset(h, -1, sizeof h);
idx = 0;
cin >> n >> m1 >> m2;
for (int i = 0; i < m1; i ++ )
{
int a, b ,c;
cin >> a >> b >> c;
add(a, b, c), add(b, a, c);
}
for (int i = 0; i < m2; i ++ )
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, -c);//虫洞是单项的而且是负权值
}
if (spfa()) cout << "YES" << endl;
else cout << "NO" << endl;
}
return 0;
}
#include
#include
using namespace std;
const int N = 1010, M = 5000 + 10;
int h[N], wt[M], ne[M], e[M], idx;
int q[N], cnt[N];
int wf[N];
double dist[N];//dist要变成double!!!
bool st[N];
int n, m;
void add(int a, int b, int c)
{
e[idx] = b, wt[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
bool check(double mid)
{
memset(dist, 0, sizeof dist);//这里求的是正环,因此求的是最长路,所以dist初始化为0,这和虫洞那题不一样
memset(st, 0, sizeof st);//多次check,所以st要初始化
memset(cnt, 0, sizeof cnt);
int hh = 0, tt = 0;
for (int i = 1; i <= n; i ++ )
{
q[tt ++ ] = i;
st[i] = true;
}
while (hh != tt)
{
int t = q[hh ++ ];
if (hh == N) hh = 0;
st[t] = false;
for (int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
if (dist[j] < dist[t] + wf[t] - mid * wt[i])//这个是由最初的公式变换来的
{
dist[j] = dist[t] + wf[t] - mid * wt[i];
cnt[j] = cnt[t] + 1;
if (cnt[j] >= n) return true;//如果mid带入能找到正环,继续找下一个正环,知道找不到正环的时候 此时mid的值才最大
if (st[j] == false)
{
q[tt ++ ] = j;
if (tt == N) tt = 0;
st[j] = true;
}
}
}
}
return false;
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ ) cin >> wf[i];
memset(h, -1, sizeof h);
for (int i = 0; i < m; i ++ )
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
}
double l = 0, r = 1e6;//r是根据我们要的结果在一个什么范围计算出来的(1000 - 1) * 1000,所有干脆取1e6了
while (r - l > 1e-4)//浮点数二分 + 根据经验值多判断两位的精度
{
double mid = (l + r) / 2;// double mid = l + r >> 1 double不能这么写
if (check(mid)) l = mid;
else r = mid;
}
printf("%.2lf", r);//printf("%.2lf", l)也行,因为while退出的时候l == r
}
#include
#include
const int N = 26 * 26 + 10, M = 1e5 + 10;
int h[N], ne[M], w[M], e[M], idx;
bool st[N];
double dist[N];
int q[N], cnt[N];
int n;
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
// bool check(double mid)
// {
// memset(st, 0, sizeof st);
// memset(cnt, 0, sizeof cnt);
// int hh = 0, tt = 0;
// for (int i = 0; i < 26 * 26; i ++ )//超级源点,把每个点都入队
// {
// q[tt ++] = i;
// st[i] = true;
// }
// int count = 0;
// while (hh != tt)
// {
// int t = q[hh ++ ];
// if (hh == N) hh = 0;
// st[t] = false;
// for (int i = h[t]; ~i; i = ne[i])
// {
// int j = e[i];
// if(dist[j] < dist[t] + w[i] - mid)
// {
// dist[j] = dist[t] + w[i] - mid;
// cnt[j] = cnt[t] + 1;
// if (++ count > 10000) return true;//trick
// if (cnt[j] >= N)
// if (st[j] == false)
// {
// q[tt ++ ] = j;
// if (tt == N) tt = 0;
// st[j] = true;
// }
// }
// }
// }
// return false;
// }
bool check(double mid)
{
memset(st, 0, sizeof st);
memset(cnt, 0, sizeof cnt);
int hh = 0, tt = 0;
for (int i = 0; i < 676; i ++ )
{
q[tt ++ ] = i;
st[i] = true;
}
int count = 0;
while (hh != tt)
{
int t = q[hh ++ ];
if (hh == N) hh = 0;
st[t] = false;
for (int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
if (dist[j] < dist[t] + w[i] - mid)
{
dist[j] = dist[t] + w[i] - mid;
cnt[j] = cnt[t] + 1;
if ( ++ count > 10000) return true; // 经验上的trick
if (cnt[j] >= N) return true;
if (!st[j])
{
q[tt ++ ] = j;
if (tt == N) tt = 0;
st[j] = true;
}
}
}
}
return false;
}
// int main()
// {
// char str[1010];
// while (scanf("%d", &n), n)
// {
// memset(h, -1, sizeof h);
// idx = 0;
// for (int i = 0; i < n; i ++ )
// {
// scanf("%s", str);
// int len = strlen(str);
// if (len >= 2)
// {
// int left = (str[0] - 'a') * 26 + str[1] - 'a';
// int right = (str[len - 2] - 'a') * 26 + str[len - 1] - 'a';
// add(left, right, len);
// }
// }
// if (!check(0)) puts("No solution");
// else
// {
// double l = 0, r = 1000;
// while (r - l > 1e-4)
// {
// double mid = (l + r) / 2;
// if (check(mid)) l = mid;
// else r = mid;
// }
// printf("%lf\n", r);
// }
// }
// return 0;
// }
int main()
{
char str[M];
while(scanf("%d", &n), n)
{
idx = 0;//多组数据需要初始化
memset(h, -1, sizeof h);
for(int i = 0; i < n; i ++ )
{
scanf("%s", &str);//把每个单词的前两个和后两个字符当作一个26进制的数字,这些数字就是一个节点
//这样不仅空间复杂度降低了,时间复杂度也降低了,我们通过一个点去遍历它的邻边的时候
//这个邻边的另一端一定是和当前节点一样的两个字符,就不用特判了
int len = strlen(str);
if (len >= 2)
{
int left = (str[0] - 'a') * 26 + (str[1] - 'a');
int right = (str[len - 2] - 'a') * 26 + (str[len - 1] - 'a');
add(left, right, len);
}
}
if (check(0) == false) puts("No solution");
else
{
double l = 0, r = 1e3;//求的是平均长度,因此二分的答案最多为1000
while(r - l > 1e-4)//浮点数二分
{
double mid = (l + r) / 2;
if (check(mid)) l = mid;//都说错了,这是浮点数二分,不用管着么多xxxxmid满足了,那么答案肯定不取mid了,左边界更新为mid + 1;
else r = mid;
}
printf("%.2lf\n", r);
}
}
return 0;
}