20200607 练习:最小费用可行流

T1 P4043 [AHOI2014/JSOI2014]支线剧情

思路:
每条边下限为 1,上限为 INF
将每个点连一条到 1 号节点,流量 INF,费用 0的边
跑上下界网络流即可

最小费用可行流:
考虑一张网络流图,每条边定义为 ( u , v , l , r , c ) (u,v,l,r,c) (u,v,l,r,c),代表从 u u u v v v 的一条有向边,费用为 c c c,容量为 [ l , r ] [l,r] [l,r] 闭区间,已知源点 s s s 汇点 t t t,且保证源点没有入边、汇点没有出边
同时定义常规费用流图的边为 ( u , v , f w , c s ) (u,v,fw,cs) (u,v,fw,cs)
现在我们需要求这张图的最小费用可行流(就是满足所有边的流量上下限制,同时费用最小)
按照如下方式建立附加边和附加点:

  1. 建立附加源点 S S SS SS,和附加汇点 T T TT TT
  2. 对于原图中每一个点(包括源汇) u u u,令 d [ u ] d[u] d[u] 代表 u u u 点的所有入边的流量下界和减去出边的流量下界和
  • 如果 d [ u ] d[u] d[u] 是负数,那么从 u u u 连一条边 ( u , T T , − d [ u ] , 0 ) (u,TT,-d[u],0) (u,TT,d[u],0) T T TT TT
  • 如果 d [ u ] d[u] d[u] 是正数,那么从 S S SS SS 连一条边 ( S S , u , d [ u ] , 0 ) (SS,u,d[u],0) (SS,u,d[u],0) u u u
  1. 对于原图中每一条边 ( u , v , l , r , c ) (u,v,l,r,c) (u,v,l,r,c),连边 ( u , v , r − l , c ) (u,v,r-l,c) (u,v,rl,c)
  2. 连边 ( t , s , I N F , 0 ) (t,s,INF,0) (t,s,INF,0)(注意这里是原图的源汇点!不是附加的源汇点!)

这样以后,从 S S SS SS T T TT TT 跑新图的最小费用最大流,再加上原图中每条边的下界流量乘以费用(必须跑的部分),就是最小费用可行流的费用了

就是消除下界影响后补流

代码:

#include <bits/stdc++.h>
using namespace std;
namespace IO {
inline char ch() {
  static char buf[1 << 21], *p1 = buf, *p2 = buf;
  return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2)
             ? EOF
             : *p1++;
}
inline int in() {
  int s = 0, f = 1;
  char x;
  for (x = ch(); x < '0' || x > '9'; x = ch())
    if (x == '-') f = -1;
  for (; x >= '0' && x <= '9'; x = ch()) s = (s * 10) + (x & 15);
  return f == 1 ? s : -s;
}
}  // namespace IO
using namespace IO;

const int A = 4e5 + 5;
const int INF = 1e9;
int n;
int head[A], tot_road;
struct Road {
  int nex, to, fw, cs;
} road[2 * A];
inline void edge(int x, int y, int z, int w) {
  road[++tot_road] = {head[x], y, z, w};
  head[x] = tot_road;
}
int d[A];

namespace NET {
#define S 0
#define T n + 1
int cur[A], h[A], tot = 1;
Road net[2 * A];
inline void ljb(int x, int y, int z, int w) {
  net[++tot] = {cur[x], y, z, w};
  cur[x] = tot;
  net[++tot] = {cur[y], x, 0, -w};
  cur[y] = tot;
}
int mf, mc;
inline void build() {
  for (int i = 1; i <= n; i++) {
    if (d[i] > 0) {
      ljb(S, i, d[i], 0);
    }
    if (d[i] < 0) {
      ljb(i, T, -d[i], 0);
    }
    for (int y = head[i]; y; y = road[y].nex) {
      int z = road[y].to, c = road[y].cs;
      ljb(i, z, INF, c);
    }
    ljb(i, 1, INF, 0);
  }
  return;
}
int dis[A], ex[A];
inline int spfa() {
  queue<int> q;
  for (int i = S; i <= T; i++) dis[i] = INF;
  dis[S] = 0, ex[S] = 1;
  q.push(S);
  while (!q.empty()) {
    int x = q.front();
    q.pop();
    ex[x] = 0;
    for (int y = cur[x]; y; y = net[y].nex) {
      int z = net[y].to, w = net[y].fw, c = net[y].cs;
      if (w && dis[z] > dis[x] + c) {
        dis[z] = dis[x] + c;
        if (!ex[z]) {
          q.push(z);
          ex[z] = 1;
        }
      }
    }
  }
  return dis[T] != INF;
}
inline int DFS(int x, int flow) {
  if (x == T) {
    mf += flow;
    return flow;
  }
  ex[x] = 1;
  int used = 0;
  for (int &y = h[x]; y; y = net[y].nex) {
    int z = net[y].to, w = net[y].fw, c = net[y].cs;
    if (w && !ex[z] && dis[z] == dis[x] + c) {
      int after = DFS(z, min(w, flow - used));
      if (after) {
        mc += after * c;
        net[y].fw -= after;
        net[y ^ 1].fw += after;
        used += after;
      }
    }
    if (used == flow) break;
  }
  ex[x] = 0;
  return used;
}
inline void ISAP() {
  mf = 0;
  while (spfa()) {
    for (int i = S; i <= T; i++) h[i] = cur[i];
    DFS(S, INF);
  }
  return;
}
}  // namespace NET

signed main() {
  n = in();
  for (int i = 1; i <= n; i++) {
    int num = in();
    for (int j = 1; j <= num; j++) {
      int x = in(), y = in();
      edge(i, x, 0, y);
      NET::mc += y;
      d[x]++, d[i]--;
    }
  }
  NET::build();
  NET::ISAP();
  printf("%d\n", NET::mc);
  return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值