学完 ISAP HLPP 之后 兴高采烈地去学费用流 结果......
居然要用 Dinic!
被气到了... 然后 打 Dinic 模板 疯狂 RE 和 TLE
然后翻到几十页找了个 Dinic 再修改成常规做法 貌似和 ISAP 差不多 就多了个 bfs
网上概念讲的都很好了 这里懒得批注 =-= 专门学最大流的去翻我的 ISAP 和 HLPP 吧
下放 Dinic 最大流 代码
#include
#include
#include
using namespace std;
const int MAXN = 10010;
const int MAXM = 200010;
int s,t,tot = 1;
int dep[MAXN],cur[MAXN],pre[MAXN],first[MAXN];
struct Edge {
int to,next,w;
} e[MAXM];
void add(int x,int y,int z)
{
e[++tot].next = first[x],first[x] = tot,e[tot].to = y,e[tot].w = z;
e[++tot].next = first[y],first[y] = tot,e[tot].to = x;
}
short bfs()
{
memset(dep,0,sizeof(dep));
int h = 0,tail = 1;
dep[s] = 1;
pre[1] = s;
while (h < tail)
{
int p = pre[++h];
//for (int a = first[p],b = e[a].to ; a ; a = e[a].next,b = e[a].to) <-要快上许多
for (int a = cur[p] = first[p],b = e[a].to ; a ; a = e[a].next,b = e[a].to)
if (e[a].w && !dep[b])
{
pre[++tail] = b;
dep[b] = dep[p] + 1;
if (b == t) return 1;
}
}
return dep[t];
}
int dfs(int p,int mx)
{
if (p == t || !mx) return mx;
int f = 0;
//for (int a = cur[p] ? cur[p] : first[p] ; a ; a = e[a].next) <-要快上许多
for (int a = cur[p] ; a ; a = e[a].next)
{
cur[p] = a;
if (e[a].w && dep[e[a].to] == dep[p] + 1)
if (f = dfs(e[a].to,min(mx,e[a].w)))
return e[a].w -= f,e[a ^ 1].w += f,f;
}
return 0;
}
int main()
{
int n,m,x,y,z;
scanf("%d%d%d%d",&n,&m,&s,&t); while (m--)
scanf("%d%d%d",&x,&y,&z),add(x,y,z);
int f = 0,ans = 0; while (bfs())
while (f = dfs(s,(1 << 30))) ans += f;
printf("%d\n",ans);
return 0;
}
然后费用流是 跑边的时候 权值从 流量 变成 流量 × 单位费用
最小费用最大流 则是在 流量最大 的前提下 使用最小的费用 意会一下
改了下原代码 =-= 然后超详细的注释在里面哈 例题仍旧是洛谷的模板 (没办法啊模板只有这里好找)
下放 Dinic 最小费用最大流 代码~
#include
#include
#include
using namespace std;
const int MAXN = 5010;
const int MAXM = 100010;
struct Edge {
int next,to,fl,co;
//邻接表存边 fl是边流量(flow) co是边单位流量的费用(cost)
} e[MAXM];
int first[MAXN],dis[MAXN],ef[MAXN],num[MAXN],pre[MAXN],que[MAXN << 3];//que就是队列
//解释:懒得解释 见子程序 见子程序 见子程序 pre:当前增广路某点是从哪个点来的(前驱)
//Tip:dis存的不是费用×流量! 因为流量每流过一条边都可能变小
short o[MAXN];
int n,s,t,tot = 1,ansf,ansc;
void add(int x,int y,int z,int w)
{
//存边与普通最大流差不多 就是费用倒过来时取相反数 即可
e[++tot].next = first[x],first[x] = tot,e[tot].to = y,e[tot].co = w,e[tot].fl = z;
e[++tot].next = first[y],first[y] = tot,e[tot].to = x,e[tot].co = -w;
}
short spfa()
{ //bfs改成SPFA
//Tip:队列不用memset可以直接覆盖
memset(dis,0x7f,sizeof(dis)); //同SPFA里的 此时(增广x次后)流到该点的最小费用 因此要更新
memset(ef,0x7f,sizeof(ef)); //可改成 ef[s] = INF 反正源点不变然后流可以覆盖
//此时 流到某点时剩余的流量(和HLPP的概念差不多 就是挤到某点的流量)
//memset(o,0,sizeof(o)); //判断某点是否在队列里 然而根据SPFA的原理此条可略
int h = 0,tail = 1; //队列 head 和 tail 初始化
que[1] = s;//这个可以放外面的说 但为了程序通俗易懂..
dis[s] = 0;//源点费用初始化
pre[t] = 0;//前驱初始化 千万别漏
while (h < tail)
{ //完全是SPFA的说..
int p = que[++h];
o[p] = 0;
for (int a = first[p],b = e[a].to ; a ; a = e[a].next,b = e[a].to)
if (e[a].fl && dis[p] + e[a].co < dis[b])
{
dis[b] = dis[p] + e[a].co;
pre[b] = p; //更新当前点的前驱
num[b] = a; //存当前边的编号 通过前驱找点可以找到该边 然后在主程序里可以更新该边的流量
ef[b] = min(ef[p],e[a].fl); //挤流量 取小的 然后以此继续推
if (!o[b]) o[b] = 1,que[++tail] = b; //p点连接的b点如果没在队列里 压进去
}
}
return pre[t];
//返回前驱 为什么不返回流量? 此处原本是Dinic的bfs 是看有无增广路的 流量存到ef里了
//如果前驱没更新到说明没增广路了 这也是pre[t]要初始化的原因
}
int main()
{
int m,x,y,z,w;
scanf("%d%d%d%d",&n,&m,&s,&t); while (m--)
scanf("%d%d%d%d",&x,&y,&z,&w),add(x,y,z,w); //强制对齐的读入
while (spfa())
{ //和Dinic一样啦 就是还能找到增广路就更新
ansf += ef[t]; //答案的流加上
ansc += ef[t] * dis[t]; //答案的费用乘上
for (int now = t ; now != s ; now = pre[now])
{
//通过前驱找该增广路经过的所有边 然后更新流量 (原路减流量反向弧加流量)
e[num[now]].fl -= ef[t];
e[num[now] ^ 1].fl += ef[t];
}
}
printf("%d %d\n",ansf,ansc); //输出
return 0;
}
拓展一下 网络流最常见的 拆点 就是把点拆成边
然后给个费用流点拆成边的例题
本题的 十字路口(这超链接不是我加的) 就是点 的意思 点不能经过多次 (处源,汇点外)
因此把中间点拆成边 中间通一个单位的流量 就是一次啦
然后源,汇点的话 因为有可能直接相连 因此也要拆 中间流量设 INF (可经过多次) 然后后面如果读入了就只会连一条流量为1的边
然后点编号 网上dalao们都是 第 i 个点 进来是 i 出去是 i + n 本蒟蒻不才 第 i 个点 进来是 i * 2 - 1 出去是 i * 2
嗯进出两点要初始化 就是 某点的进 对应 某点的出
然后读边是 前者的出 对应着 后者的进
其他和费用流无异啦
这里只注释新加的地方 费用流的看上面那篇
下放代码~
#include
#include
#define INF 1 << 30
using namespace std;
const int MAXN = 410; //因为点拆边点成了两倍 因此..
const int MAXM = 41000; //同上 然后试过了40100都不行...建议边范围开两倍多一些(不是一点)
struct Edge {
int next,to,fl,co;
} e[MAXM];
int first[MAXN],dis[MAXN],ef[MAXN],pre[MAXN],que[MAXN << 3];
int s = 1,t,tot = 1,ansf,ansc;
short o[MAXN];
void add(int x,int y,int z,int w)
{
e[++tot].next = first[x],first[x] = tot,e[tot].to = y,e[tot].co = w,e[tot].fl = z;
e[++tot].next = first[y],first[y] = tot,e[tot].to = x,e[tot].co = -w;
}
int min(int x,int y)
{
return x < y ? x : y;
}
short spfa()
{
memset(dis,0x7f,sizeof(dis));
ef[1] = INF;
dis[1] = 0;
int h = 0,tail = 1;
while (h < tail)
{
int p = que[++h];
o[p] = 0;
for (int a = first[p],b = e[a].to ; a ; a = e[a].next,b = e[a].to)
if (e[a].fl && dis[p] + e[a].co < dis[b])
{
dis[b] = dis[p] + e[a].co;
pre[b] = a;
ef[b] = min(ef[p],e[a].fl);
if (!o[b]) o[b] = 1,que[++tail] = b;
}
}
return dis[t] != dis[0]; //判断到t点花费变了没 没变说明增广不到 返回0
}
int main()
{
int n,m,x,y,w;
scanf("%d%d",&n,&m);
t = n * 2; //确定拆点后点最大范围
for (int a = 2 ; a < n ; ++ a)
add(a * 2 - 1,a * 2,1,0); //非源,汇点拆点流量设为1 因为这点只能经过一次
add(1,2,INF,0); //源点可经过多次 流量设为INF
add(n * 2 - 1,n * 2,INF,0); //汇点也可经过多次 流量设为INF
while (m--)
{
scanf("%d%d%d",&x,&y,&w);
add(x * 2,y * 2 - 1,1,w);
}
que[1] = 1; //队首是源点 init~
while (spfa())
{
ansf += ef[t];
ansc += ef[t] * dis[t];
for (int a = t,b = pre[a] ; a != s ; a = e[b ^ 1].to,b = pre[a])
{/*
这个循环做的是真好 我翻到的
开始条件: 从汇点往回 通过pre数组找到经过的边的编号 赋值给b
循环中: 找到的边除去此次增广路的流量
判断: 懒得说了=-=
下一轮循环赋值: 通过反向边找到与a节点相连的上一个节点 然后pre找边赋给b
就是这么巧妙*/
e[b].fl -= ef[t];
e[b ^ 1].fl += ef[t];
}
}
printf("%d %d\n",ansf,ansc);
return 0;
}