1. 程式人生 > 其它 >洛谷 P3199 [HNOI2009]最小圈(01分數規劃,spfa判負環)

洛谷 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
這樣就不用再看出題人臉色了