网络流最明显的特征就是:既像dp,又像贪心
一些匹配向问题都可以往网络流方向想(棋盘交换,攻击伤害,工作安排,消除矛盾)
最大流
正难则反,大变小,流化割
黑白染色,可以解决相邻结点不形容的问题
一定要有限流(人+菜+房间,人放中间限制流量)【经典例题】
行列成点(缩点)
时间的推移可以枚举,每次新建一层点,上一层的点连向下一层的后继点表示时间的推移
二分图两部分的点一般不是一模一样的,如果是的话,我们可以考虑在中间先限流
在集合中选出一些点,使得两两之间没有冲突,可以考虑最小割(“最小割最小代价消除矛盾”,集合划分问题)
二分图,可以解决最大匹配,最小覆盖,所有点均经过等问题
费用流
手写队列
费用随次数有差别的时候,可以考虑拆边
【经典模型】等待时间的累加(第i个人完成第j个任务,是第i个人完成的倒数第k个任务)
这个问题还有进一步优化(因为每次只找一个增广路,所以我们可以增广一次连一次边)
退流
经典例题
退流,实际上就是判断并证明一条边在最大流中的必要性
只有这条边满流,而且一定在最大流中出现时(没有其他增广路能连接原边的两个端点),退流算法才会选择删除这条边
假如要删除
from−>to
f
r
o
m
−
>
t
o
这条边(保证
from−>to
f
r
o
m
−
>
t
o
这条边在原图中存在)
第一步就是
dfs(from,to)
d
f
s
(
f
r
o
m
,
t
o
)
,走残量网络判断是否有其他路径连接
from,to
f
r
o
m
,
t
o
如果不连通,我们就可以选择删除这条边了
删边前,暴力退流:
T−>to,from−>S
T
−
>
t
o
,
f
r
o
m
−
>
S
退完流可能整个图不再满足流量平衡的限制,但是并不影响下一步的退流操作(还是可以得到正确答案的)
网络流经典模型
DINIC
const int INF=0x33333333;
const int N=100010;
struct node{
int x,y,v,nxt;
};
node way[N<<1];
int st[N],tot=0,cur[N],deep[N];
int q[N];
void add(int u,int w,int z) {
tot++;way[tot].x=u;way[tot].y=w;way[tot].v=z;way[tot].nxt=st[u];st[u]=tot;
tot++;way[tot].x=w;way[tot].y=u;way[tot].v=0;way[tot].nxt=st[w];st[w]=tot;
}
int bfs(int s,int t) {
int tou,wei;
tou=wei=0;
for (int i=s;i<=t;i++) cur[i]=st[i];
memset(deep,-1,sizeof(deep));
q[++wei]=s; deep[s]=1;
while (tou<wei) {
int now=q[++tou];
for (int i=st[now];i!=-1;i=way[i].nxt)
if (way[i].v&&deep[way[i].y]==-1) {
deep[way[i].y]=deep[now]+1;
q[++wei]=way[i].y;
}
}
return deep[t]!=-1;
}
int dfs(int now,int t,int limit) {
if (now==t||!limit) return limit;
int flow=0,f;
for (int i=cur[now];i!=-1;i=way[i].nxt) {
cur[now]=i;
if (way[i].v&&deep[way[i].y]==deep[now]+1&&(f=dfs(way[i].y,t,min(limit,way[i].v))))
{
flow+=f;
limit-=f;
way[i].v-=f;
way[i^1].v+=f;
if (!limit) break;
}
}
return flow;
}
int dinic(int s,int t) {
int ans=0;
while (bfs(s,t))
ans+=dfs(s,t,INF);
return ans;
}
void init() {
memset(st,-1,sizeof(st));
tot=-1;
}
费用流
注意时刻维护pre数组,以及增广的时候是在pre边上操作
const int INF=0x33333333;
const int N=100010;
struct node{
int x,y,v,c,nxt;
};
node way[N<<1];
int st[N],tot=0,dis[N],pre[N];
int q[N];
bool in[N];
void add(int u,int w,int z,int cc) {
tot++;way[tot].x=u;way[tot].y=w;way[tot].v=z;way[tot].c=cc;way[tot].nxt=st[u];st[u]=tot;
tot++;way[tot].x=w;way[tot].y=u;way[tot].v=0;way[tot].c=-cc;way[tot].nxt=st[w];st[w]=tot;
}
int spfa(int s,int t) {
int tou,wei;
tou=wei=0;
memset(in,0,sizeof(in));
memset(dis,0x33,sizeof(dis));
q[++wei]=s;
dis[s]=0; in[s]=1;
while (tou<wei) {
int now=q[++tou];
in[now]=0;
for (int i=st[now];i!=-1;i=way[i].nxt)
if (way[i].v) {
if (dis[way[i].y]>dis[now]+way[i].c) {
dis[way[i].y]=dis[now]+way[i].c;
pre[way[i].y]=i; //注意维护转移边
if (!in[way[i].y])
q[++wei]=way[i].y,in[way[i].y]=1;
}
}
}
return dis[t]!=INF;
}
int solve(int s,int t) {
int ans=0;
while (spfa(s,t)) {
int sum=INF;
for (int i=t;i!=s;i=way[pre[i]].x)
sum=min(sum,way[pre[i]].v); //注意边的编号
ans=dis[t]*sum;
for (int i=t;i!=s;i=way[pre[i]].x)
way[pre[i]].v-=sum,
way[pre[i]^1].v+=sum;
}
return ans;
}