1. 程式人生 > >洛谷3288 SCOI21方伯伯運椰子(分數規劃+spfa)

洛谷3288 SCOI21方伯伯運椰子(分數規劃+spfa)

紀念部落格又一次爆炸了

首先,對於本題中,我們可以發現,保證存在正整數解,就表示一定費用會降低。又因為一旦加大的流量,費用一定會變大,所以總流量一定是不變的

那麼我們這時候就需要考慮一個退流的過程

對於原圖每一條\(u->v,c>0\)的邊,我們在新圖中建一條\(v->u,價值是a-d\)
表示退這個流要花費的費用,相當於退流的過程

對於原圖任意一條\(u->v\)的邊,我們在新圖中建一條\(u->v,價值是b+d\)的邊,相當於擴流的過程

那麼只有成環的時候,才能滿足流量平衡這個條件。

正好和消圈定理相類似

所謂消圈定理
就是在某個流 f 中,如果其對應的殘餘網路沒有負圈(剩餘流量為 0 的邊視為不存在)
那它一定就是當前流量下的最小費用流。
反之亦然。
即「f 是最小費用流等價於其殘餘網路中沒有負圈」。

那根據題目要求的是個比例,那我們一定是隻修改最大的那個環就行。

那麼我們考慮分數規劃一下

二分\(mid <= max(\frac{x-y}{k})\)

\[mid\times k\le x-y\]
\[mid\times k + (y-x) \le 0\]

由於在一個環中,k就是這個環的大小,我們可以考慮把每個\(mid\)分配到每個邊,也就是轉化成了

每條邊的權值在原來新圖的基礎上\(+mid\),然後\(check\)是否存在負(0)環

這時候直接上\(spfa\)就好,
不過之前的問題轉化,還是很有難度啊

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define mk makr_pair
#define ll long long
using namespace std;
inline int read()
{
  int x=0,f=1;char ch=getchar();
  while (!isdigit(ch)) {if (ch=='-') f=-1;ch=getchar();}
  while (isdigit(ch)) {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
  return x*f;
}
const int maxn = 50010;
const int maxm = 1e6+1e2;
struct Node{
 int u,v,a,b,c,d;
};
Node a[maxn];
int point[maxn],nxt[maxm],to[maxm];
int inque[maxn];
int vis[maxn];
double val[maxm];
queue<int> q;
int cnt,n,m;
double dis[maxn];
int x[maxm],y[maxm];
double w[maxm];
double l=0,r=1e9;
int tmp;
double ans;
bool flag;
int s,t;
void addedge(int x,int y,double w)
{
 //cout<<x<<" "<<y<<" "<<w<<endl;
 nxt[++cnt]=point[x];
 to[cnt]=y;
 val[cnt]=w;
 point[x]=cnt; 
}
void spfa(int s)
{
 while (!q.empty()) q.pop();
    for (int i=1;i<=n;i++) dis[i]=1e9;
 memset(inque,0,sizeof(inque));
 memset(vis,0,sizeof(vis));
 dis[s]=0;
 inque[s]=1;
 q.push(s);
 while (!q.empty())
 {
  int x=q.front();
  q.pop();
  vis[x]=0;
  inque[x]++;
  if (inque[x]>=n+1)
  {
   flag=true;
   return;
  }
  //cout<<1<<endl;
  for (int i=point[x];i;i=nxt[i])
  {
   int p =  to[i];
   if (dis[p]>=dis[x]+val[i])
   {
    dis[p]=dis[x]+val[i];
    if (!vis[p])
    {
     q.push(p);
     vis[p]=1;
    }
   }
  }
 }
}
bool check(double mid)
{
 cnt=0;
 flag=false; 
 memset(point,0,sizeof(point));
 for (int i=1;i<=tmp;i++)
   addedge(x[i],y[i],w[i]+mid);
    spfa(n-1);
    if (flag) return true;
    else return false;
}
int main()
{
  n=read(),m=read();
  n+=2;
  for (int i=1;i<=m;i++)
  {
     int u=read(),v=read(),a=read(),b=read(),c=read(),d=read();
   ++tmp;
   x[tmp]=u;
   y[tmp]=v;
   w[tmp]=b+d;
   if (c>0)
   {
    ++tmp;
    x[tmp]=v;
    y[tmp]=u;
    w[tmp]=a-d;
   } 
  }
 // cout<<check(103)<<endl;
 // return 0;
  while (r-l>1e-3){
   double mid = (l+r)/2;
   if (check(mid)) ans=mid,l=mid;
   else r=mid;
  }
  printf("%.2lf\n",ans);
  return 0;
}