洛谷 P3199 [HNOI2009]最小圈(01分數規劃,spfa判負環)
阿新 • • 發佈:2021-09-28
傳送門
解題思路
概括一下題意:求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
這樣就不用再看出題人臉色了