洛谷 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
这样就不用再看出题人脸色了