【BZOJ】1486 [HNOI2009]最小圈
【算法】二分+spfa
【题解】据说这个叫分数规划?
二分答案a,则对于任意的环有w/k≤a即w-ak≤0,若满足条件则a变小,否则a变大。
因为w=w1+w2+...+wk,所以变形为(w1-a)+(w2-a)+...+(wk-a)≤0。于是问题转化为在图中找负环。
不过由于spfa限制,“=”没办法并入"<",但是由于精度足够,最后也就是1.00000000001≈1.00000000。
使用DFS的spfa:可以在发现更新到之前更新过的节点就认为是负环(从x跑出去最后又回来更新x,说明跑的这段路是负数)。
spfa的d数组(最短路)全部初始化为0(即只更新路径为负的),相当于设置一个起点向所有点连容量为0的边,因为是全图找负环。
确认某个曾访问的节点是祖先,这是DFS的特性和优势。
精度问题:107要求精确到10-8即log(1015)/log(2)=49,所以跑60次二分就能保证精度没问题了。
因为一个memset的size是double(以为是int)调了2小时……QAQ
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; const int maxn=3010,maxm=30010; const double eps=1e-11; struct edge{int from,v;double w;}e[maxm]; int n,m,first[maxn],tot=0; double d[maxn],w[maxm]; bool vis[maxn],flag; void insert(int u,int v,double w) {tot++;e[tot].v=v;e[tot].w=w;e[tot].from=first[u];first[u]=tot;} void spfa(int x) { vis[x]=1; for(int i=first[x];i;i=e[i].from) if(!flag&&d[e[i].v]>d[x]+e[i].w) { if(vis[e[i].v]) { flag=1; break; } d[e[i].v]=d[x]+e[i].w; spfa(e[i].v); } vis[x]=0; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { int u,v; scanf("%d%d%lf",&u,&v,&w[i]); insert(u,v,w[i]); } double l=-10000000,r=10000000; while(r-l>eps) { double mid=(l+r)/2; flag=0; memset(d,0,8*(n+1)); memset(vis,0,(n+1)); for(int i=1;i<=tot;i++)e[i].w=w[i]-mid; for(int i=1;i<=n;i++)if(!flag)spfa(i); if(flag)r=mid;else l=mid; } printf("%.8lf",l); return 0; }