【SCOI2014】方伯伯运椰子
题面
https://loj.ac/problem/2214
一道好题。
最小费用最大流+调整法($orz$杨颙)(原谅我想到杨颙,好像我们好久没见了)
先假设所有的路都被压缩至$w=0$,然后建两类边,用最小费用最大流扩大(类似的思想:棋盘占领)。
注意求出的东西应该是后来的费用$Y - $之前的费用$X + k \times mid$,所以$check()$的时候要取反(这个地方我想了好久。。。。。)
#include<cstdio> #include<iostream> #include<cstring> #include<vector> #include<queue> #include<cmath> #define ri register int #define N 5050 #define M 3050 #define INF 1000000007 #define LL long long #define T (n+2) #define S (n+1) using namespace std; int n,m; int u[M],v[M],a[M],b[M],c[M],d[M]; struct graph { vector<int> to,w,ed[N]; vector<double> c; double dis[N]; int cur[N]; bool vis[N]; void clear() { c.clear(); to.clear(); w.clear(); for (ri i=1;i<=n+2;i++) ed[i].clear(); } void add_edge(int a,int b,int aw,double ac) { to.push_back(b); w.push_back(aw); c.push_back(ac); ed[a].push_back(to.size()-1); to.push_back(a); w.push_back(0); c.push_back(-ac); ed[b].push_back(to.size()-1); } bool spfa() { for (ri i=1;i<=n+2;i++) dis[i]=100000000000000.7; memset(vis,0,sizeof(vis)); queue<int> q; dis[S]=0;q.push(S);vis[S]=1; while (!q.empty()) { int x=q.front(); q.pop(); for (ri i=0;i<ed[x].size();i++) { int e=ed[x][i]; if (dis[to[e]]>dis[x]+c[e] && w[e]) { dis[to[e]]=dis[x]+c[e]; if (!vis[to[e]]) vis[to[e]]=1,q.push(to[e]); } } vis[x]=0; } return dis[T]<100000000000000.7; } int dfs(int x,int lim) { if (x==T || !lim) return lim; LL sum=0; vis[x]=1; for (ri &i=cur[x];i<ed[x].size();i++) { int e=ed[x][i]; if (fabs(dis[x]+c[e]-dis[to[e]])<1e-5 && w[e] && !vis[to[e]]) { int f=dfs(to[e],min(lim,w[e])); w[e]-=f; w[1^e]+=f; lim-=f; sum+=f; if (!lim) return sum; } } return sum; } double zkw() { double ret=0; while (spfa()) { memset(vis,0,sizeof(vis)); memset(cur,0,sizeof(cur)); ret+=dfs(S,INF)*dis[T]; } return ret; } } G; bool can(double mid) { double dec=0; for (ri i=1;i<=m;i++) dec+=(a[i]-d[i]+mid)*c[i]; for (ri i=1;i<=m;i++) { G.add_edge(u[i],v[i],c[i],-(a[i]+mid-d[i])); if (u[i]!=S) G.add_edge(u[i],v[i],INF,b[i]+mid+d[i]); } if (0-(dec+G.zkw())>0) return 1; else return 0; } int main() { scanf("%d %d",&n,&m); for (ri i=1;i<=m;i++) { scanf("%d %d %d %d %d %d",&u[i],&v[i],&a[i],&b[i],&c[i],&d[i]); } double lb=0,rb=50000.0; double ans=0; while (rb-lb>1e-3) { double mid=(lb+rb)/2; //printf("%.5lf %.5lf %.5lf\n",lb,rb,mid); G.clear(); if (can(mid)) lb=ans=mid; else rb=mid; } printf("%.2lf\n",ans); return 0; }