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[]数组一定是要清空的;
所以上面的情况,估计是数据水罢了