spfa负环 P2868

https://daniu.luogu.org/problem/show?pid=2868
spfa这个常见的算法其实理解的深不深刻,好的题目可以反映出来;
这道题目我们就是在一个有向图里面找环;
这个环的 节点值和/边值和 要求经可能大
那么可以理解为
对于每一个环有一个
节点值和/边值和(设为w)
我们要让某一个环的w>ans(其实应该是>=,但是足够精度下差不多)
求ans的最大值
那么我们可以二分ans,再把边全变成
ans*边值和-节点值和
这样的话我们只要用spfa取找负负环就可以了;

往常spfa就是用来做最短路的,所以在做这题的时候思想就被限制住了;
我们根据spfa的定义,不难得到一种方法
在跑spfa的时候记录一下每一个点的进对次数,假如超过n次那么就有负环,因为一个点最坏情况下会被(n-1)个点更新;
但是这样时间爆炸
于是人们就相处了一种更快的方法,基于dfs的spfa
毕竟bfs和dfs是互通的
在dfs的时候我们像tarjan一样标记当前路径上的点;
显然,如果我们dfs到一个已经被包括的点,那么是不是出现了一个环
那么如果这个点被自己在的环又更新了一次,这个环是不是负环?
这样我们仅仅需要找到一个负环就可以了
先放代码

 #include<bits/stdc++.h>
#define Ll long long
using namespace std;
const Ll N=1e3+5;
struct{int to,nxt,v;}a[N*5];
int head[N],ll,v[N];
bool in[N];
int n,m,x,y,z;
double l,r,mid,d[N];
void init(int x,int y,int z){a[++ll].nxt=head[x];a[ll].v=z;a[ll].to=y;head[x]=ll;}
bool spfa(int x){
    in[x]=1;
    for(int k=head[x];k;k=a[k].nxt)
        if(d[a[k].to]>d[x]+mid*a[k].v-(double)v[a[k].to]){
            d[a[k].to]=d[x]+mid*a[k].v-(double)v[a[k].to];
            if(in[a[k].to]||spfa(a[k].to)){
                in[x]=0;return 1;
            }
        }
    in[x]=0;return 0;
}
bool check(){
    for(int i=1;i<=n;i++)
        if(spfa(i))return 1;
    return 0;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)scanf("%d",&v[i]),r+=v[i];
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&x,&y,&z);
        init(x,y,z);
    }
    while(r-l>=1e-9){
        mid=(l+r)/2;
        if(check())l=mid;else r=mid;
    }
    printf("%.2lf",l);
}

于是我们惊讶的发现,对于每一次spfa,我们都没有重置d[]数组??
原因很简单的,我们并不是求最短路,我们是找负环,所以我们的d[]数组对我们没有任何的影响,他只要有一个初始值,那么负环就可以被检查出来,换句话说,这里的d[]数组,已经失去充当最短路的功能了

我之后又做了一道题目,后来发现如果边权在变大变小,那么d[]数组一定是要清空的;
所以上面的情况,估计是数据水罢了

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值