图论——spfa判负环

负环

图 G G G中存在一个回路,该回路边权之和为负数,称之为负环。

spfa求负环

方法1 :统计每个点入队次数, 如果某个点入队n次, 说明存在负环。

证明:一个点入队n次,即被更新了n次。一个点每次被更新时所对应最短路的边数一定是递增的,也正因此该点被更新n次那么该点对应的的最短路长度一定大于等于n,即路径上点的个数至少为n+1。根据抽屉原理,路径中至少有一个顶点出现两次, 也就是路径中存在环路。 而算法保证只有距离减少才会更新, 所以环路权值之和为负数。

方法2 :统计从起点到任意顶点最短路经过的边数, 若某点对应边数 c n t ≥ n cnt≥n cnt≥n, 则也说明负环。

方法2根据抽屉原理易证。

我们一般采用方法2才求负环。

acwing904.虫洞

spfa求负环模板题

cpp 复制代码
#include <iostream>
#include <cstring>
using namespace std;
const int N = 510, M = 5210;
int h[N], w[M], e[M], ne[M], tot;
int dist[N];
int cnt[N];
bool st[N];
int q[N];
int t;
int n, m, k;
void add(int a, int b, int c)
{
    e[tot] = b, ne[tot] = h[a], w[tot] = c, h[a] = tot ++ ;
}
bool spfa()
{
    memset(dist, 0, sizeof dist);
    memset(st, 0, sizeof st);
    memset(cnt, 0, sizeof cnt);
    int hh = 0, tt = 0;
    for (int i = 1; i <= n; i ++ )
    {
        st[i] = 1;
        q[tt ++ ] = i;
    }
    while (hh != tt)
    {
        int t = q[hh ++ ];
        if (hh == N) hh = 0;
        st[t] = 0;
        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]) continue;
                st[j] = 1;
                q[tt ++ ] = j;
                if (tt == N) tt = 0;
            }
        }
    }
    return false;
}
int main()
{
    cin >> t;
    while (t -- )
    {
        cin >> n >> m >> k;
        tot = 0;
        memset(h, -1, sizeof h);
        while (m -- )
        {
            int a, b, c;
            cin >> a >> b >> c;
            add(a, b, c), add(b, a, c);
        }
        while (k -- )
        {
            int a, b, c;
            cin >> a >> b >> c;
            add(a, b, -c);
        }
        bool t = spfa();
        if (t) puts("YES");
        else puts("NO");
    }
    return 0;
}

acwing361.观光奶牛

本题要求我们求出环中存在的 ∑ f i ∑ t i \frac{\sum fi}{\sum ti} ∑ti∑fi的最大值,如果直接对问题进行求解是有难度的,考虑二分答案。首先易知,答案的范围在 1 / 1000 , 1000 1/1000, 1000 1/1000,1000之间,假设我们当前二分到的答案为 m i d mid mid,如果 a n s < m i d ans< mid ans<mid,我们可以去左半区间进行寻找,反之我们去右半区间寻找。
∑ f i ∑ t i > m i d \frac{\sum fi}{\sum ti}> mid ∑ti∑fi>mid
∑ f i > m i d ∗ ∑ t i \sum fi> mid*\sum ti ∑fi>mid∗∑ti
∑ f i − m i d ∗ ∑ t i > 0 \sum fi-mid*\sum ti> 0 ∑fi−mid∗∑ti>0
∑ ( f i − m i d ∗ t i ) > 0 \sum (fi-mid*ti)> 0 ∑(fi−mid∗ti)>0
∑ ( m i d ∗ t i − f i ) < 0 \sum (mid*ti-fi)< 0 ∑(mid∗ti−fi)<0

经过转换后,问题就等价于把图中边权换为 m i d ∗ t i − f i mid*ti-fi mid∗ti−fi,然后判断图中是否存在负环,存在负环则说明图中存在 ∑ f i ∑ t i > m i d \frac{\sum fi}{\sum ti}>mid ∑ti∑fi>mid的环。

cpp 复制代码
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1010, M = 5010;
int h[N], e[M], ne[M], tot;
int wf[N], wt[M];
int q[N];
double dist[N];
int cnt[N];
bool st[N];
int n, m;
void add(int a, int b, int c)
{
    e[tot] = b, ne[tot] = h[a], wt[tot] = c, h[a] = tot ++ ;
}
bool check(double x)
{
    memset(dist, 0, sizeof dist);
    memset(st, 0, sizeof st);
    memset(cnt, 0, sizeof cnt);
    int hh = 0, tt = 0;
    for (int i = 1; i <= n; i ++ )
    {
        st[i] = 1;
        q[tt ++ ] = i;
    }
    while (hh != tt)
    {
        int t = q[hh ++ ];
        if (hh == N) hh = 0;
        st[t] = 0;
        for (int i = h[t]; ~i; i = ne[i])
        {
            int j = e[i];
            double w = x * wt[i] - wf[t];
            if (dist[j] > dist[t] + w)
            {
                dist[j] = dist[t] + w;
                cnt[j] = cnt[t] + 1;
                if (cnt[j] == n) return true;
                if (st[j]) continue;
                st[j] = 1;
                q[tt ++ ] = j;
                if (tt == N) tt = 0;
            }

        }
    }
    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 = 1; i <= m; i ++ )
    {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c);
    }
    double l = 0, r = 1001;
    while (r - l > 1e-4)
    {
        double mid = (l + r) / 2;
        if (check(mid)) l = mid;
        else r = mid;
    }
    printf("%.2lf", l);
    return 0;
}

acwing1165.单词环

我们第一感觉是把每一个单词看作一个节点,这样一来节点总数 n = 1 0 5 n=10^5 n=105,最坏情况是所有单词都可以互相进行匹配,这样一来边数 m = 1 0 5 ∗ 1 0 5 = 1 0 10 m=10^5*10^5=10^{10} m=105∗105=1010,考虑其他建图方式。

我们可以把每个单词看作一条边,这样一来边数 m = 1 0 5 m=10^5 m=105,每个单词开头结尾两个字母为节点,节点总数 n = 26 ∗ 26 = 676 n=26*26=676 n=26∗26=676。

本题要求我们求所有环的 ∑ w i ∑ 1 \frac{\sum wi}{\sum 1} ∑1∑wi最大值。和上题相同,二分答案,答案区间为 1 / 1000 , 1000 1/1000,1000 1/1000,1000
∑ w i ∑ 1 > m i d \frac{\sum wi}{\sum 1}> mid ∑1∑wi>mid
∑ w i > m i d \sum wi> mid ∑wi>mid
∑ w i − m i d > 0 \sum wi-mid> 0 ∑wi−mid>0
∑ ( w i − m i d ) > 0 \sum (wi-mid)> 0 ∑(wi−mid)>0
∑ ( m i d − w i ) < 0 \sum (mid-wi)< 0 ∑(mid−wi)<0

经过转换后,问题就等价于把图中边权换为 m i d − w i mid-wi mid−wi,然后判断图中是否存在负环,存在负环则说明图中存在 ∑ w i ∑ 1 > m i d \frac{\sum wi}{\sum 1}>mid ∑1∑wi>mid的环。

cpp 复制代码
#include <iostream>
#include <cstring>
using namespace std;
const int N = 26 * 26 + 10, M = 100010;
int h[N], e[M], ne[M], w[M], tot;
double dist[N];
bool st[N];
int cnt[N];
int q[N];
char s[1010];
int n;
void add(int a, int b, int c)
{
    e[tot] = b, ne[tot] = h[a], w[tot] = c, h[a] = tot ++ ;
}
bool check(double mid)
{
    memset(st, 0, sizeof st);
    memset(dist, 0, sizeof dist);
    memset(cnt, 0, sizeof cnt);
    int hh = 0, tt = 0;
    for (int i = 0; i < 26 * 26; i ++ )
    {
        q[tt ++ ] = i;
        st[i] = 1;
    }
    int count = 0;
    while (hh != tt)
    {
        int t = q[hh ++ ];
        st[t] = 0;
        if (hh == N) hh = 0;
        for (int i = h[t]; ~i; i = ne[i])
        {
            int j = e[i];
            double ww = mid - w[i];
            if (dist[j] > dist[t] + ww)
            {
                dist[j] = dist[t] + ww;
                cnt[j] = cnt[t] + 1;
                if (++ count == 10000) return true;
                if (cnt[j] == N) return true;
                if (st[j]) continue;
                st[j] = 1;
                q[tt ++ ] = j;
                if (tt == N) tt = 0;
            }
        }
    }
    return false;
}
int main()
{
    while (cin >> n, n)
    {
        memset(h, -1, sizeof h);
        tot = 0;
        for (int i = 1; i <= n; i ++ )
        {
            cin >> s;
            int len = strlen(s);
            if (len < 2) continue;
            int ll = (s[0] - 'a') * 26 + s[1] - 'a';
            int rr = (s[len - 2] - 'a') * 26 +s[len - 1] - 'a';
            add(ll, rr, len);
        }
        double l = 0, r = 1001;
        if (!check(0)) puts("No solution");
        else{
            while (r - l > 1e-4)
            {
                double mid = (l + r) / 2;
                if (check(mid)) l = mid;
                else r = mid;
            }
            printf("%.2lf\n", r);
        }
    }
    return 0;
}
相关推荐
通信小呆呆2 小时前
当算法有了“五感”:多模态数据融合如何向人体感官协同学习?
人工智能·学习·算法·机器学习·机器人
benben0442 小时前
强化学习之DQN算法族(基于gymnasium开发)
算法
何以解忧,唯有..3 小时前
Go语言循环语句详解:for、range与循环控制
开发语言·算法·golang
想吃火锅10054 小时前
【leetcode】88.合并两个有序数组js
算法
生成论实验室5 小时前
机器人:一个自主运动的系统
人工智能·算法·语言模型·机器人·自动驾驶·agi·安全架构
Qres8215 小时前
算法复键——树状数组
数据结构·算法
H178535090965 小时前
SolidWorks第四部分_直接实体建模特征9_替换面原理
线性代数·算法·机器学习·3d建模·solidworks
不会就选b5 小时前
算法日常・每日刷题--<二分查找>3
算法
绿算技术5 小时前
Mooncake 与绿算ForinnBase GroundPool如何联手打破推理僵局?
科技·算法·架构