经典题
如果把每个环都找一遍绝对时间爆炸
所以我们要换一种思路
看到求最大最小首先考虑二分答案
如果平均权值最小的回路小于我们二分的答案mid会发生什么呢
如果我们把回路的长度减少 mid*回路边数,回路的长度就会变成负数
而把回路减少 mid*边数 其实相当于把回路上的每条边都减少mid
减完后图中就出现了负环,用spfa可以判负环
所以复杂度就是 o(log($len_{max}-len_{min}$) * 玄学)...
注意图的联通性,每个联通块都要判一波负环
别忘了可能图没环
#include<iostream> #include<cstdio> #include<algorithm> #include<cmath> #include<cstring> #include<queue> using namespace std; typedef long long ll; inline int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x*f; } const int N=5007; const double eps=1e-4;//精度要求不高 int n,m; int fir[N],from[N<<1],to[N<<1],val[N<<1],cntt; inline void add(int a,int b,int c) { from[++cntt]=fir[a]; fir[a]=cntt; to[cntt]=b; val[cntt]=c; } inline bool pd(double x,double y) { return x-y>eps ? 1 : 0; }//判断x是否大于y double dis[N]; int cnt[N]; bool vis[N],P[N]; inline bool spfa(int st,double mid)//spfa判负环 { queue <int> q; memset(cnt,0,sizeof(cnt)); memset(dis,127,sizeof(dis)); q.push(st); vis[st]=1; dis[st]=0.0; while(!q.empty()) { int x=q.front(); q.pop(); vis[x]=0; P[x]=1; for(int i=fir[x];i;i=from[i]) { int &v=to[i]; if( pd(dis[v],dis[x]+val[i]-mid) ) { dis[v]=dis[x]+val[i]-mid; cnt[v]=cnt[x]+1; if(cnt[v]>=n) return 1;//如果有负环返回1 if(!vis[v]) { q.push(v); vis[v]=1; } } } } return 0; } inline bool check(double mid)//判合法性 { bool flag=0; memset(P,0,sizeof(P)); for(int i=1;i<=n&&!flag;i++) flag|=spfa(i,mid);//每个联通块都要判 return !flag; } int T; int main() { T=read(); for(int j=1;j<=T;j++)//多组数据 { memset(fir,0,sizeof(fir)); cntt=0; memset(from,0,sizeof(from)); memset(val,0,sizeof(val)); int a,b,c; double l=1e8,r=-1e8,mid;//l,r是最短边和最长边 n=read(); m=read(); for(int i=1;i<=m;i++) { a=read(); b=read(); c=read(); add(a,b,c); l=min(l,(double)c); r=max(r,(double)c); } if(check(r+1)) { printf("Case #%d: No cycle found. ",j); continue; }//判断是否有环 while(pd(r,l))//二分答案 { mid=(l+r)/2; if(check(mid)) l=mid; else r=mid; } printf("Case #%d: %.2lf ",j,l); } return 0; }
其实这算是个简单的$01$规划问题了