洛谷 P3199 [HNOI2009]最小圈(01分数规划,spfa判负环)

传送门


解题思路

概括一下题意:求min(sumw/sumn),其中sumw表示一个环上的边权和,sumn表示一个环上点的数量。

这种分数规划问题很常见的一个套路为二分答案,然后转化成01分数规划。

即二分一个比值k,判断有无环满足sumw/sumn<=k,也就是sumw-sumn*k<=0。进一步整理一下,得到:

[sum_{i=1}^{n}(w-k)leq 0 ]

于是我们就可以把所有的边权减去k,然后判断有无负环!
spfa一遍即可。
这个题bfs版的会被卡死,需要写dfs版的。

AC代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#include<iomanip>
using namespace std;
const int maxn=4005;
const double inf=1e9+5;
const double eps=1e-9;
int n,m,cnt,p[maxn],vis[maxn];
double dis[maxn],l=-1e7,r,maxw,mid;
struct node{
	int v,next;
	double w;
}e[30005];
void insert(int u,int v,double w){
	cnt++;
	e[cnt].v=v;
	e[cnt].w=w;
	e[cnt].next=p[u];
	p[u]=cnt;
}
void recover(double x){
	for(int i=1;i<=m;i++){
		e[i].w+=x;
	}
}
bool spfa(int u){
	vis[u]=1;
	for(int i=p[u];i!=-1;i=e[i].next){
		int v=e[i].v;
		if(dis[v]>=dis[u]+e[i].w){
			dis[v]=dis[u]+e[i].w;
			if(vis[v]||!spfa(v)) return 0;
		}
	}
	vis[u]=0;
	return 1;
}
bool check(double x){
	for(int i=1;i<=m;i++) e[i].w-=x;
	memset(dis,0,sizeof(dis));
	memset(vis,0,sizeof(vis));
	for(int i=1;i<=n;i++){
		if(!spfa(i)){
			recover(x);
			return 1;
		}
	}
	recover(x);
	return 0;
}
int main(){
	ios::sync_with_stdio(false);
	memset(p,-1,sizeof(p));
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int u,v;double w;
		cin>>u>>v>>w;
		insert(u,v,w);
		maxw=max(w,maxw);
	}
	r=maxw;
	while(r-l>eps){
		double mid=(l+r)/2;
		if(check(mid)) r=mid;
		else l=mid;
	}
	cout<<fixed<<setprecision(8)<<l;
	return 0;
}

另一种做法

_rqy学姐提供了另一种不会被卡的做法:
直接放博客链接https://www.cnblogs.com/y-clever/p/7043553.html
这样就不用再看出题人脸色了

原文地址:https://www.cnblogs.com/yinyuqin/p/15349788.html