写一篇网络流费用流的总结吧。
最大流板子:dinic()
bfs求深度后dfs找增广,要求只能向深度+1的点转移
bool bfs()
{
memset(dep,-1,sizeof(dep));
dep[s]=0;
queue<int>q;
q.push(s);
while(!q.empty())
{
int now=q.front();
q.pop();
for(int i=h[now];i;i=fr[i])
{
if(f[i]&&dep[to[i]]==-1)
{
dep[to[i]]=dep[now]+1;
q.push(to[i]);
}
}
}
return dep[t]!=-1;
}
int dfs(int x,int r)
{
if(x==t)return r;
int ans=0;
for(int i=h[x];i;i=fr[i])
{
if(f[i]&&dep[to[i]]==dep[x]+1)
{
int res=dfs(to[i],min(r,f[i]));
if(res>0)
{
//cout<<x<<' '<<to[i]<<endl;
r-=res;
f[i]-=res;
f[i^1]+=res;
ans+=res;
if(!r)return ans;
}
}
}
if(!ans)dep[x]=-1;
return ans;
}
int dinic()
{
int flow=0;
while(bfs())
{
flow+=dfs(s,1e9);
}
return flow;
}
费用流板子:spfa或者dijk(好像叫什么原始对偶算法)
spfa和dijk都用来找最短(长)路并记录一下路径,这相当于在保证最小费用的情况下找增广。spfa没什么好说的,dijk的话因为有负权边,直接求会产生错误,这时就要加一个数组h,称为势。假设要从x扩展到y,边长本来应为w,在势的影响下,我们重新定义边长为h[x]-h[y]+w,这就要求我们的h数组满足规律h[x]-h[y]+w>=0,我们发现我们求的dis数组刚好也有这个规律,因此我们把上次扩展完后得到的dis数组作为本次的h数组,非常优美。
用spfa和dijk本质上一样,不过在找最短路方面dijk肯定更稳定,不过一般的题数据规模小,没什么必要。
bool spfa()
{
memset(dis,0x80,sizeof(dis));
dis[s]=0;
queue<int>q;
q.push(s);
while(!q.empty())
{
int now=q.front();
q.pop();
vis[now]=0;
for(int i=h[now];i;i=fr[i])
{
if(f[i]&&dis[to[i]]<dis[now]+w[i])
{
dis[to[i]]=dis[now]+w[i];
pre[to[i]]=i;
prn[to[i]]=now;
if(!vis[to[i]])
{
vis[to[i]]=1;
q.push(to[i]);
}
}
}
}
return dis[t]!=dis[3999];
}
int mfmc()
{
int ans=0;
while(spfa())
{
int x=t,res=1e9;
while(x!=s)
{
res=min(res,f[pre[x]]);
x=prn[x];
}
x=t;
while(x!=s)
{
f[pre[x]]-=res;
f[pre[x]^1]+=res;
x=prn[x];
}
//cout<<"fuck";
ans+=res*dis[t];
}
return ans;
}
一些常见套路:
1.拆点,很多时候我们要限制一个点的使用次数,这个时候可以把,每个点拆成入点和出点,中间连一条流量为这个点可用次数的边。还有一种毒瘤情况就是每个点在路径上和路径结尾效果不同,这是要拆成三个点,入点原点出点顺次连接,原点和源点汇点相连,这样一个点在路径中间和两端的效果就不同了。比如这个题
有一个n行m列的黑白棋盘,你每次可以交换两个相邻格子(相邻是指有公共边或公共顶点)中的棋子,最终达到目标状态。要求第i行第j列的格子只能参与mi,j次交换。
我们首先把白点看成没有,那么只要把黑点放到对的地方即可。我们发现,每个由白变黑的点最后从四周的点流来了1,向t流了1,(当然还有途径他的一些流量,这部分是平衡的),同理又黑变白的点把s流来的1流出去了。这样子,我们可以把每个点拆成两个,让每个点被经过的次数为他的流量,费用为1。但是这样有一个问题,就是在一个黑点的移动过程中,路程中的点实际被经过了两次,而两头的点仅被经过一次,为了体现这个差别,我们就要像上面说的那样拆成三个点,入点和原点间、原点和出点间流量为原流量一半。
2.输出方案。输出路径的话dfs残留网络,如果反边有剩余流量就表示要走这条边。求最小割的方案其实是看最后一次bfs时那些点和源点联通,哪些点和汇点联通。
3.黑白染色,常见于棋盘上,把点分为内部不相互影响的两个集合,比如方格取数。分成两个集合后就可以连边跑一些最小割之类的东西。这个东西其实叫什么最大权闭合子图问题,也就是当我们要舍弃一些价值时,建立一个有依赖关系的图(不一定是二分图),跑最小割,那么割掉的那些边显然就是我们拿了不划算的。
题目
这个嘛,观察之后黑点放东西影响的都是白点,所以把超级源点连向每个黑点,每个白点连向超级汇点,每个黑点连向影响的白点,这样跑了最小割后,就相当于我们把一些不值得拿的位置割掉了。最后用总的能放棋子的地方数减去最小割,就是答案。
4.按时间拆点,在每个时间点上都把每个节点建一个复制,然后从前一秒可转移过来的位置转移过来。比如这个题
按照时间拆点后可以保证图的层次,其实和一般的拆点思路差不多。
这个题我们显然不能直接搞,所以在每个时间点都把地球,月球,空间站建一遍,然后从小到大枚举答案,每次从相应的飞船的上一个位置连向这一秒的位置,容量为飞船容量,并且每个点都从他上一秒的对应位置连过来,容量inf,相当于空间站容量无限。这个图建起来就长这样