图(五):SPFA判断负环

SPFA–负环

一、模板

spfa判断负环

参考代码

/*
spfa 判负环 
    1、原理与bellman-ford一样 抽屉原理
    2、dist[x]=dist[t]+w[i]  cnt[x]=cnt[t]+1;记录从1到x的边数 
       若cnt[x]则有抽屉原理可以得出 出现了闭环 又因为有负权边则一定为负环 
*/
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N=2010,M=10010;
int n,m;
int h[N],e[M],ne[M],w[M],idx;
int dist[N],cnt[N];
bool st[N];
void add(int a,int b,int c){
    e[idx]=b;
    w[idx]=c;
    ne[idx]=h[a];
    h[a]=idx++;
}
bool spfa(){
    queue<int> q;
    for(int i=1;i<=n;i++){
        q.push(i);
        st[i]=true;
    }
    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(dist[j]>(dist[t]+w[i])){
                cnt[j]=cnt[t]+1;
                dist[j]=dist[t]+w[i];
                if(cnt[j]>=n){
                    return true;
                }
                if(!st[j]){
                    q.push(j);
                    st[j]=true;
                }
            }
        }
    }
    return false;
}
int main(){
    scanf("%d%d",&n,&m);
    memset(h,-1,sizeof(h));
    while(m--){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }
    if(spfa()){
        printf("Yes\n");
    }
    else{
        printf("No\n");
    }
    return 0;
}

二、应用

虫洞

题面:

在这里插入图片描述

思路:

由题意得有两种边,一种为边权为正的双向边,另一种为边权为负的单向边。约翰想要想要看到出发前的自己,则当图中存在负环时约翰一定可以回到过去,而不存在负环时则一定不能回到过去。故本题即为判断负环,若存在负环则输出yes 否则输出no。

参考代码:

#include<iostream>
#include<cstring>
#define int long long
using namespace std;
const int N = 510, M = 1e5 + 10;
int n, m1, m2;
int h[N], e[M], ne[M], w[M], idx;
int dist[N];
int q[N], cnt[N];
bool st[N];


void add(int a,int b, int c){
    e[idx] = b;
    w[idx] = c;
    ne[idx] = h[a];
    h[a] = idx ++;
}
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 ++){
        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 != -1; 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]){
                    q[tt ++ ] = j;
                    if(tt == N){
                        tt = 0;
                    }
                    st[j] = true;
                }
            }
        }
    }
    return false;
}
signed main(){
    int T;
    scanf("%lld",&T);
    while(T --){
        scanf("%lld%lld%lld",&n, &m1, &m2);
        memset(h, -1, sizeof(h));
        idx = 0;
        for(int i = 0; i < m1; i ++){
            int a, b, c;
            scanf("%lld%lld%lld",&a, &b, &c);
            add(a, b, c);
            add(b, a, c);
        }
        for(int i = 0; i < m2; i ++){
            int a, b, c;
            scanf("%lld%lld%lld",&a, &b, &c);
            add(a, b, -c);
        }
        if(spfa()){
            printf("YES\n");
        }
        else{
            printf("NO\n");
        }
    }
}

01分数规划

在图论中形如 ∑ f i ∑ t i \frac{\sum{f_i}}{\sum{t_i}} tifi求最大值称为01分数规划问题,即有点权又有边权求最短路,则可以将点权放到边权中。

步骤: (1)、 二分定点mid (2)、重新定义边权。

∑ f i ∑ t i > m i d = ∑ f i − m i d ∗ ∑ t i > 0 = ∑ ( f i − m i d ∗ t i ) > 0 \frac{\sum{f_i}}{\sum{t_i}} > mid = \sum{f_i} - mid * \sum{t_i} > 0 = \sum{(f_i - mid * t_i)} > 0 tifi>mid=fimidti>0=(fimidti)>0

观光奶牛

**题意:**给定一张 LL 个点、PP 条边的有向图,每个点都有一个权值 f[i]f[i],每条边都有一个权值 t[i]t[i]。求图中的一个环,使“环上各点的权值之和”除以“环上各边的权值之和”最大。输出这个最大值。注意:数据保证至少存在一个环。

**思路:**浮点数二分查找出答案mid, 若图中存在一个正环则 r = mid (因为是找最大值), 否则 l = mid。

#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
typedef pair<int,int> PII;
const int N = 1e4 + 10;
vector<PII> g[N];
int n, m;
int wf[N];
int q[N];
int cnt[N];
int st[N];
double dist[N];
bool check(double mid){
    memset(st, false, sizeof st);
    memset(dist, 0, sizeof dist);
    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(auto son : g[t]){
            int j = son.first;
            if(dist[j] < (dist[t] + wf[t] - mid * son.second)){
                dist[j] = dist[t] + wf[t] - mid * son.second;
                cnt[j] = cnt[t] + 1;
                if(cnt[j] >= n){
                    return true;
                }
                if(!st[j]){
                    q[tt ++] = j;
                    if(tt == N){
                        tt = 0;
                    }
                    st[j] = true;
                }
            }
        }
    }
    return false;
    
}
int main(){
    scanf("%d%d",&n, &m);
    for(int i = 1; i <= n; i ++){
        scanf("%d",&wf[i]);
    }
    while(m --){
        int a, b, c;
        scanf("%d%d%d",&a, &b, &c);
        g[a].push_back({b, c});
    }
    double l = 0, r = 1010;
    while(r - l > 1e-4){
        double mid = (l + r) / 2.0;
        if(check(mid)){
            l = mid;
        }
        else{
            r = mid;
        }
    }
    printf("%.2lf\n", r);
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值