【题解】 [HNOI2009] 最小圈 (01分数规划,二分答案,负环)
题目背景
如果你能提供题面或者题意简述,请直接在讨论区发帖,感谢你的贡献。
题目描述
对于一张有向图,要你求图中最小圈的平均值最小是多少,即若一个圈经过k个节点,那么一个圈的平均值为圈上k条边权的和除以k,现要求其中的最小值
输入输出格式
输入格式:第一行2个正整数,分别为n和m
以下m行,每行3个数,表示边连接的信息,
输出格式:一行一个数,表示最小圈的值,保留8位小数。
输入输出样例
说明
若设边权为vvv,那么n≤3000,m≤10000,v≤50000n\le 3000,m\le 10000,v\le 50000n≤3000,m≤10000,v≤50000
题解转自NaVi_Awson巨佬博客(快去访问 http://www.cnblogs.com/NaVi-Awson/p/7641518.html
题解
最小化平均值($01$分数规划)。
使用二分求解。对于一个猜测的$mid$,只需判断是否存在平均值小于$mid$的回路。
如何判断?
假设存在一个包含$k$条边的回路,回路上各边权值为$w_1$ ,$w_2$ ,$...$,$w_k$ ,那么平均值小于$midv$意味着:
$$w_1 +w_2 +...+w_k <k×mid$$
即:
$$(w_1 -mid)+(w_2 -mid)+...+(w_k -mid)<0$$
换句话说,只要把边$(a,b)$的权$w(a,b)$改成$w(a,b)-mid$,再判断新图中是否有负环即可。
存在负环,那么之前的不等式满足,即存在着更小的平均值,$r=mid$;不存在,$l=mid$。
不要脸的贴自己的代码:
//It is coded by Ning_Mew on 10.26 #include<iostream> #include<cstdio> #include<cmath> #include<cstring> #include<algorithm> #include<cstdlib> using namespace std; const double CDN=0.000000001; int n,m; double L,R,mid; double dist[10000+10]; int head[3000+10],t; struct Edge{ int next,to; double dis; }edge[10000+10]; void add(int from,int to,double dis){ edge[++t].next=head[from]; edge[t].to=to; edge[t].dis=dis; head[from]=t; } bool vis[3000+10]; void clear(){ memset(vis,false,sizeof(vis)); memset(dist,0,sizeof(dist)); return; } bool SPFA(int u){ vis[u]=true; for(int i=head[u];i!=0;i=edge[i].next){ int v=edge[i].to; if(dist[v]>dist[u]+edge[i].dis-mid){ if(vis[v]){vis[u]=false;return true;} dist[v]=dist[u]+edge[i].dis-mid; if(SPFA(v)){vis[u]=false;return true;} } } vis[u]=false;return false; } bool check(){ clear(); for(int i=1;i<=n;i++){if(SPFA(i))return true;} return false; } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=m;i++){ int x,y,z; scanf("%d%d%d",&x,&y,&z); add(x,y,z); R=max(R,(double)z); } while(L+CDN<R){ mid=(L+R)/2; if(check()){R=mid;} else L=mid; } printf("%0.8lf",(L+R)/2); return 0; }