WR:网流之神的传奇一生
\(\LARGE\text{“}\) 采菊东篱下,波撼岳阳城。\(\large {_”}\)
试见过,拟阵之交,因神的剑术沸腾爆裂。
试见过,资本之恶,因神的力量化雪纷飞。
是神的全知,让无人知晓的文明之始得以传世。
是神的全能,让牢不可破的数理之树兔死狐悲。
最大流
显然。\(O(n^2m)\)。
费用流
显然。\(O(nmf)\)。
上下界
loj 上有这三体的模板,懒得放链接了 = =
可行流
新建超源 \(SS\),超汇 \(TT\)。
对于一条边 \(p\to q:[l,r]\),那就相当于保证要 \(p\) 点必须要流出 \(l\) 的流量,\(q\) 点必须要得到 \(l\) 的流量,然后跑 \(p\to q: r-l\) 的最大流。
于是我们就让 \(p\) 点连向 \(TT\),边权为 \(l\);\(SS\) 点连向 \(q\) 边权为 \(l\)。然后跑 Dinic。如果最大流不是所有的 \(l\) 之和,就说明有 \(p\) 没有贡献出 \(l\) 的流量,或者有 \(q\) 没有收到 \(l\) 的流量。(不然这些 \(l\) 的流量,会存在于 \(SS,TT\) 的连边中)。
实际操作中,我们给每个点设一个数组 hav
代表要导入/导出的流量总和。如果大于 0 就是要导入的流量,连边 \(SS\to i:|hav_i|\);小于 0 就是要导出的流量,连边 \(i\to TT:|hav_i|\)。然后判断是否 Dinic = \(\sum w_i\)。
最大流
大体思路:先跑出一组可行流,再在残量网络上继续跑,看看能不能再流一些流量。
首先连边 \(T\to S:\inf\),这样就可以看成没有源点/汇点的情况了(因为源点是无限输出,汇点是无限输入)然后按照上文以 \(SS,TT\) 为源汇跑可行流。
这样我们得到了一组可行解。将 \(T\to S:\inf\) 断边。再以 \(S,T\) 为源汇继续跑一遍最大流即可。
最小流
参考最大流的做法,但是最后不是“再流一些流量”,而是要“再退回流量”。
于是把最后一步改为:以 \(S\) 为汇,以 \(T\) 为源(也就是 swap(S,T)
),跑一遍最大流。答案就是可行流减去最大流。
最长反链
https://www.luogu.com.cn/problem/P4298 然而不会第二问。
首先根据胡克定律(Dilworth定理):最长反链 = 最小可重链覆盖。
然后我们对原图跑一遍传递闭包,就会最长反链 = 最小不可重链覆盖。(感性理解一下)
那么就可以这样理解:一开始每个点都是独立的链,每次可以把两条链首尾相接,最多可以操作几次。
发现,一个点在最终的链上,最多有一个前驱和一个后继,那么就把一个点拆成两个点 \(x_{in},x_{out}\)。对于一条边 \(x\to y\) 连边 \(x_{out}\to y_{in}\)(十分好理解!)。于是就有一个二分图,左边是全部的 out,右边是全部的 in。跑二分图最大匹配即可。假设最大匹配为 \(val\),答案就是 \(n-val\)。
最大权闭合子图
题意
有一个有向图,每一个点都有一个权值(可以为正或负或0),选择一个权值和最大的子图,使得每个点的后继都在子图里面,这个子图就叫最大权闭合子图。
题解
https://blog.csdn.net/can919/article/details/77603353
二分图最大匹配 - 匈牙利算法
DFS
#include<bits/stdc++.h>
#define rep(i,x,y) for(int i=x;i<=y;++i)
#define per(i,x,y) for(int i=x;i>=y;--i)
#define mar(o) for(int E=fst[o];E;E=e[E].nxt)
#define v e[E].to
#define lon long long
using namespace std;
const int n7=101234,m7=1012345;
struct dino{int to,nxt;}e[m7];
int n1,n2,m,ecnt,fst[n7],ans,tim,vis[n7],mat[n7];
int rd(){
int shu=0;bool fu=0;char ch=getchar();
while( !isdigit(ch) ){if(ch=='-')fu=1;ch=getchar();}
while( isdigit(ch) )shu=(shu<<1)+(shu<<3)+ch-'0',ch=getchar();
return fu?-shu:shu;
}
void edge(int p,int q){
ecnt++;
e[ecnt]=(dino){q,fst[p]};
fst[p]=ecnt;
}
bool dfs(int o){
vis[o]=tim;
mar(o){
if(mat[v])continue;
mat[o]=v,mat[v]=o;
return 1;
}
mar(o){
int z=mat[v];
if(vis[z]==tim)continue;
mat[o]=v,mat[v]=o,mat[z]=0;
if( dfs(z) )return 1;
mat[o]=0,mat[v]=z,mat[z]=v;
}
return 0;
}
int main(){
n1=rd(),n2=rd(),m=rd();
rep(i,1,m){
int p=rd(),q=n1+rd();
edge(p,q),edge(q,p);
}
rep(i,1,n1){
if(!mat[i])tim++,dfs(i);
}
rep(i,1,n1){
if(mat[i])ans++;
}
printf("%d\n",ans);
return 0;
}
BFS
#include<bits/stdc++.h>
#define rep(i,x,y) for(int i=x;i<=y;++i)
#define per(i,x,y) for(int i=x;i>=y;--i)
#define mar(o) for(int E=fst[o];E;E=e[E].nxt)
#define v e[E].to
#define lon long long
using namespace std;
const int n7=101234,m7=1012345;
struct dino{int to,nxt;}e[m7];
int n1,n2,m,ecnt,fst[n7],ans,tim,vis[n7],mat[n7],pre[n7];
int head,tail,que[n7];
int rd(){
int shu=0;bool fu=0;char ch=getchar();
while( !isdigit(ch) ){if(ch=='-')fu=1;ch=getchar();}
while( isdigit(ch) )shu=(shu<<1)+(shu<<3)+ch-'0',ch=getchar();
return fu?-shu:shu;
}
void edge(int p,int q){
ecnt++;
e[ecnt]=(dino){q,fst[p]};
fst[p]=ecnt;
}
void aug(int o){
while(o){
int tmp=mat[ pre[o] ];
mat[o]=pre[o];
mat[ pre[o] ]=o;
o=tmp;
}
}
void bfs(int o0){
tim++;
head=tail=1,que[head]=o0,vis[o0]=tim;
while(head<=tail){
int o=que[head];head++;
mar(o){
if(vis[v]==tim)continue;
pre[v]=o;
if(!mat[v]){aug(v);return;}//return 1;
else{
vis[v]=vis[ mat[v] ]=tim;
tail++,que[tail]=mat[v];
}
}
}
//return 0
}
int main(){
n1=rd(),n2=rd(),m=rd();
rep(i,1,m){
int p=rd(),q=n1+rd();
edge(p,q),edge(q,p);
}
rep(i,1,n1){
if(!mat[i])bfs(i);
}
rep(i,1,n1){
if(mat[i])ans++;
}
printf("%d\n",ans);
return 0;
}
如果边带权怎么办?费用流!(似乎也有匈牙利做法,然而懒。)
(但是如果有负权边可能会影响,要删除此边)
一般图最大匹配 - 带花树
https://www.luogu.com.cn/blog/happydef-blog/yi-ban-tu-zui-da-pi-pei-xue-xi-bi-ji
\(O(nm)\)。(如果算上常数小的像 \(O(0)\) 的并查集,那就是 \(O(nm\log)\) 的)。
UPD:NT 才写带花树。直接用随机化稳过而且还好写:
- 重复十次二分图最大匹配;
- 每次枚举一个点的出边前,random_shuffle 一下边集。
gym102268A - Angle Beats
https://codeforc.es/gym/102268
题意
给你一个 \(n\times m\) 的网格图,每个点为 + * .
中的一个。一个 +
与四周任意两个 .
匹配,一个 *
可以与一个横方向和一个纵方向的 .
匹配(就是匹配形成一个 L 字形)。
求最大匹配 \(n,m\le 100\)。
题解
对于 *
拆成两个点,四周的一个 .
与这两个点连边。对于 +
也拆成两个点,横向的 .
与第一个点连边,纵向的点与另一个点连边。
然后拆成的两个点也要互相连边,这是为了如果出现
+ .
. +
的情况,可能会两个加号分别匹配两个点。所以连边就会使得不匹配有 1 的贡献,那么遇到上述的情况,就会优先不连边。
于是这样就保证了正确性,直接跑 FakeBloomingTree 即可。
UPD:不能跑 FakeBloomingTree 会被卡,要用真正的 BloomingTree!