01分数规划问题+spfa判断负环。
二分一个答案ans,将图中的每条边的权值都减去ans,如果存在负环,说明ans还可以更小;否则ans应当更大。
但是直接用spfa判断负环,对于每一个ans最慢是O(|V||E|),会TLE。
那么我们可以直接dfs判负环,每次只走使d[y]减小的路,当走到一个点发现他已经被访问过,则一定存在负环。
#include <iostream> #include <algorithm> #include <cstring> #include <cstdio> #include <cstdlib> #include <queue> using namespace std; int n,m,tot,in[3005],v[3005],h[3005],f; double d[3005],l,r; struct edge { int y,ne; double v; }e[10005]; void Addedge(int x,int y,double v) { tot++; e[tot].y=y; e[tot].ne=h[x]; e[tot].v=v; h[x]=tot; } void dfs(int x,double k) { in[x]=1; for (int i=h[x];i;i=e[i].ne) { int y=e[i].y; if (d[y]>d[x]+e[i].v-k) { if (in[y]) {f=0;return;} d[y]=d[x]+e[i].v-k; dfs(y,k); if (!f) return; } } in[x]=0; } bool judge(double k) { f=1; for (int i=1;i<=n;i++) { dfs(i,k); if (!f) return false; } return true; } void Solve() { double ans; while (r-l>1e-10) { double m=(l+r)/(double)2; for (int i=1;i<=n;i++) in[i]=0,d[i]=0.0; if (!judge(m)) r=m; else ans=l,l=m; } printf("%.8lf\n",ans); } double maxx(double a,double b) { if (a<b) return b; return a; } double minn(double a,double b) { if (a<b) return a; return b; } int main() { //freopen("in.in","r",stdin);freopen("out.out","w",stdout); scanf("%d%d",&n,&m); r=(double)-1e7; l=(double)1e7; for (int i=1;i<=m;i++) { int x,y; double w; scanf("%d%d%lf",&x,&y,&w); Addedge(x,y,w); r=maxx(r,w),l=minn(l,w); } Solve(); return 0; }
感悟:
1.改成dfs还TLE是因为并没有完全理解dfs这种做法,d[]的初始值都设成了inf,实际上这样比spfa还要慢。
d[]的初始值都设成0,遇到使他为负的才继续走,大大减少了递归层数。
为什么一定能找到负环?
我们假设存在负环,但没有找到负环,则这个环一定可以分成若干段>=0的部分(只有这样才会停止dfs),但是这与负环的权值和<0矛盾。
因此,假设不成立。