D114 最短路→负环 01分数规划+SPFA 算法 P2868 [USACO07DEC] Sightseeing Cows G 观光奶牛

D114 最短路→负环 01分数规划+SPFA 算法 P2868 [USACO07DEC] Sightseeing Cows G 观光奶牛_哔哩哔哩_bilibili

 

P2868 [USACO07DEC] Sightseeing Cows G - 洛谷

有向图,既有点权又有边权,求一个环,使环上的点权之和除以边权之和的比值最大。

思路

最大化比值,显然是 01分数规划。我们二分一个 mid,然后判定图上是否存在一个环,该环是否满足

$\frac {\sum f(i)} {\sum w(i)} > mid$,即 $\sum (f(i)-w(i)*mid)>0$,即 $\sum (w(i)*mid-f(i))<0$

把 “点权化入边权” 计算,把 $w(i)*mid-f(i)$ 看做新边权,即等效边权之和 $<0$,即图中存在一个 “负环”

每次二分跑一遍 SPFA 判是否存在负环,若存在说明 mid 偏小,则 $l=mid$,否则 $r=mid$

点权、边权的数据范围均为 [1,1000],所以比值的范围应为 (0,1000]

image

玄学优化:找环,栈比队列快!因为环上的点一旦入栈,就会出栈,顺藤摸瓜,扩展一圈找出环

相关板子:

A07【模板】01分数规划 - 董晓 - 博客园

D113【模板】最短路→负环 SPFA 算法 P2850 [USACO06DEC] Wormholes G 虫洞 - 董晓 - 博客园

 

// 二分+SPFA 算法 O(24*N*M)
#include<bits/stdc++.h>
using namespace std;

const int N=1010,M=5010;
int h[N],to[M],ww[M],ne[M],idx;
void add(int a,int b,int c){
  to[++idx]=b,ww[idx]=c,ne[idx]=h[a],h[a]=idx;
}
int n,m;
int f[N]; //点权
double d[N];
int cnt[N],vis[N];

bool spfa(double mid){
  memset(d,0,sizeof d);
  memset(vis,0,sizeof vis);
  memset(cnt,0,sizeof cnt);
  stack<int> q; //栈比队列快
  for(int i=1; i<=n; i++) q.push(i),vis[i]=true;
  
  while(!q.empty()){
    int u=q.top();q.pop();vis[u]=false;
    for(int i=h[u]; i; i=ne[i]){
      int v=to[i];
      double w=ww[i]*mid-f[u]; //等效边权
      if(d[v]>d[u]+w){
        d[v]=d[u]+w;
        cnt[v]=cnt[u]+1;
        if(cnt[v]>=n) return true; //有负环
        if(!vis[v]) q.push(v),vis[v]=true;
      }
    }
  }
  return false;
}
int main(){
  cin>>n>>m;
  for(int i=1; i<=n; i++) cin>>f[i]; //点权
  for(int i=0,a,b,c; i<m; i++){
    cin>>a>>b>>c;
    add(a,b,c);
  }
  
  double l=0,r=1000;
  while(r-l>1e-4){
    double mid=(l+r)/2;
    if(spfa(mid)) l=mid;
    else r=mid;
  }
  printf("%.2lf\n",r);
}

 

posted @ 2026-03-25 14:30  董晓  阅读(48)  评论(0)    收藏  举报