网络流学习笔记
注:笔者是蒟蒻,所以本文几乎是干货,枯燥无味甚至可能会引人不适,请读者谨慎阅读。
为了笔者快爆掉的肝点个赞好吗???
Part.1 网络流基础定义
一个有向带权图 G = ( V , E ) G=(V,E) G=(V,E) 是一个网络当且仅当:
- 图中只有一个入度为 0 0 0 的点,即源点 S S S;
- 图中只有一个出度为 0 0 0 的点,即汇点 T T T;
- 每条边 u → v u\to v u→v 的权值为正数,这个数表示此边的容量,记作 c u , v c_{u,v} cu,v。
网络流可以具象为流水的系统,源点就是水源,能无限的供应水,而汇点就是取水点,无限的消耗水,边就是管道,而且通过水的数量不能超过其容量。
形式化的讲,记在边 u → v u\to v u→v 之间的流量为 f u , v f_{u,v} fu,v。若边在原图中存在,则会有如下性质:
- 0 < f u , v ≤ g u , v 0<f_{u,v}\le g_{u,v} 0<fu,v≤gu,v;
- f u , v = − f v , u f_{u,v}=-f_{v,u} fu,v=−fv,u;
- 对于一个点 u u u,和所有 x → u x\to u x→u 的 x x x 以及 u → y u\to y u→y 的 y y y,有 ∑ x f x , u = ∑ y f u , y \sum\limits_{x} f_{x,u}=\sum\limits_{y} f_{u,y} x∑fx,u=y∑fu,y,这个性质被称作流量守恒(即流入多少水就流出多少水)。
增广路定义为一条从 S S S 到 T T T 剩余容量大于 0 0 0 的路径。
残量网络定义为已经流过一些流量后删除满流的边的网络。
Part.2 最大流及其应用
模板题。
定义最大流为在满足上述性质的前提下,流入汇点的流量总和的最大值。重点在于如何求这个值。
首先有一个贪心,就是每次选择一个增广路,流出路径上的剩余流量的最小值,然后更新剩余流量。
上述算法显然是错误的,那我们如何补救呢?
考虑对于每个边建立反向边,初始的容量为 0 0 0。每次增广时,一条边容量减 x x x,其反向边容量加 x x x。结合下图理解:
可以发现,建立反向边后,相当于给了一次撤销的机会,而上述假贪心加上这个优化过后就是对的了,笔者并不会证明。这个算法就是 FF 算法,时间复杂度与流量有关。
而每次选择源点到汇点的最短增广路进行增广,就得到了 EK 算法,时间复杂度 O ( n m 2 ) O(nm^2) O(nm2)。
每次只增广一条太亏了,所以把所有最短路径一起增广。具体的,以 S S S 为起点对图 BFS,然后按距离分层,就只需要考虑相邻层之间的边了。从 S S S 开始携带 ∞ \infty ∞ 的流量向下遍历,每次枚举当前节点的邻居,尽量将手里的流量送出去。
注意到有效边组成的图一定是个 DAG,说明如果将一条边的剩余容量变成 0 0 0,这条边就不会再访问了。所以可以开个数组 n o w u now_u nowu 表示 u u u 访问到那个邻居了,DFS 的时候就从 n o w u now_u nowu 开始遍历后面的边。
这个小优化叫做当前弧优化,加上这个优化的算法叫做 Dinic。其时间复杂度就降到了 O ( n 2 m ) O(n^2m) O(n2m),而且跑不满( n n n 开 1 0 4 10^4 104, m m m 开 1 0 5 10^5 105 都可以干过去),特别的,在二分图中,Dinic 的时间复杂度为 O ( m n ) O(m\sqrt n) O(mn)。这是 OI 中最常用的最大流算法。
放一下板子的代码:
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e2+5,M = 5e3+5;
int n,m,s,t,cnt = 1,head[N],to[M<<1],nxt[M<<1],g[M<<1];//cnt初始值为1方便获得反边
inline void add(int x,int y,int z)
{nxt[++cnt] = head[x];head[x] = cnt;to[cnt] = y,g[cnt] = z;
}
int dis[N],now[N];
inline bool bfs()
{for(int i = 1;i<=n;i++) dis[i] = -1;queue<int> q;q.push(s);dis[s] = 0;now[s] = head[s];while(!q.empty()){int u = q.front();q.pop();for(int i = head[u];i;i = nxt[i]){int v = to[i];if(g[i]>0&&dis[v]==-1){q.push(v);dis[v] = dis[u]+1,now[v] = head[v];if(v==t) return 1;}}}return 0;
}
int dfs(int u,int s)
{if(u==t) return s;int k,res = 0;for(int i = now[u];i;i = nxt[i]){if(!s) break; int v = to[i];now[u] = i;//当前弧优化 if(g[i]>0&&dis[v]==dis[u]+1)//能流且位于下一层 {k = dfs(v,min(s,g[i]));if(k==0) dis[v] = 2e9;g[i]-=k,g[i^1]+=k,res+=k,s-=k;} }return res;
}
signed main()
{ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);cin>>n>>m>>s>>t; for(int i = 1,u,v,w;i<=m;i++)cin>>u>>v>>w,add(u,v,w),add(v,u,0);int ans = 0;while(bfs()) ans+=dfs(s,2e9);cout<<ans;return 0;
}
最大流的用处太多了,后文慢慢讲吧。
Part.3 费用流及其应用
模板题。
费用流其实就是在最大流上对于每个弧增加了一个属性:费用,每流过一个单位的流就会消耗对应的费用。让你求在流最大的情况下使得费用最少或最大。
先想想费用流中的反向边怎么建,其实就是正向边的费用和反向边的费用成相反数就行了。
那怎么增广呢?还记得最大流中的 EK 算法吗,这个算法在求最大流是不是寻找的最短的增广路吗,其实改到费用流里就每次把费用最少的增广路拿出来增广就行了(这里是求最小费用最大流)。然而由于图有负边权,所以只能跑 SPFA。
时间复杂度是 O ( n m f ) O(nmf) O(nmf),其中 f f f 指最大流。这个很容易理解,就是最多增广 f f f 次,每次增广时瓶颈在于 SPFA,所以是 O ( n m f ) O(nmf) O(nmf) 的。这个大大的跑不满,由于在题目中建出来的图比较特殊(最大流可能比较小), n n n 开 1 0 4 10^4 104, m m m 开 1 0 5 10^5 105 都可以干过去。这就是名言“最大流不卡 Dinic,费用流不卡 EK”的由来。
放代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 5e3+5,M = 1e5+5;
int n,m,s,t,cnt = 1,head[N],now[N],nxt[M],to[M],g[M],fl[M];
inline void add(int x,int y,int w,int flow)
{nxt[++cnt] = head[x];head[x] = cnt;to[cnt] = y,g[cnt] = w,fl[cnt] = flow;nxt[++cnt] = head[y];head[y] = cnt;to[cnt] = x,g[cnt] = -w,fl[cnt] = 0;
}
int dis[N],mn[N];
bool vis[N];
inline bool spfa()
{for(int i = 1;i<=n;i++)dis[i] = 2e9,vis[i] = 0;dis[s] = 0,mn[s] = 2e9,vis[s] = 1;queue<int> q;q.push(s);while(!q.empty()){int u = q.front();q.pop();vis[u] = 0;for(int i = head[u];i;i = nxt[i]){int v = to[i],w = g[i];if(fl[i]&&dis[v]>dis[u]+w){now[v] = i,dis[v] = dis[u]+w,mn[v] = min(mn[u],fl[i]);if(!vis[v]) vis[v] = 1,q.push(v);}}}if(dis[t]!=2e9) return 1;return 0;
}
signed main()
{ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);cin>>n>>m>>s>>t;for(int i = 1,u,v,w,flow;i<=m;i++)cin>>u>>v>>flow>>w,add(u,v,w,flow);int ans1 = 0,ans2 = 0;while(spfa()){ans1+=mn[t],ans2+=mn[t]*dis[t];int x = t;while(x!=s){fl[now[x]]-=mn[t],fl[now[x]^1]+=mn[t];x = to[now[x]^1];}}cout<<ans1<<' '<<ans2<<'\n';return 0;
}
至于最大费用最大流的求法,你可以更改一下 SPFA 或者将费用取反写最小费用最大流都可以。
还是来几道例题吧(可能比较多,读者可以选择性阅读)。
例题一:P2045。
考虑将每个点 ( x , y ) (x,y) (x,y) 拆分为 i n x , y , o u t x , y in_{x,y},out_{x,y} inx,y,outx,y,建立超级原点 S S S,汇点为 o u t n , n out_{n,n} outn,n。连一条 S → i n 1 , 1 S\to in_{1,1} S→in1,1,费用为 0 0 0,容量为 k k k 的边,表示最多走 k k k 次。对于一个点 ( x , y ) (x,y) (x,y),有如下几种边:
- 一条 i n x , y → o u t x , y in_{x,y}\to out_{x,y} inx,y→outx,y,费用为 a x , y a_{x,y} ax,y,容量为 1 1 1 的边(只能取一次),表示将 ( x , y ) (x,y) (x,y) 中的数取出来;
- 一条 i n x , y → o u t x , y in_{x,y}\to out_{x,y} inx,y→outx,y,费用为 0 0 0,容量为 ∞ \infty ∞ 的边,表示直接跳过 ( x , y ) (x,y) (x,y);
- 一条 o u t x , y → i n x + 1 , y out_{x,y}\to in_{x+1,y} outx,y→inx+1,y 或者 o u t x , y → i n x , y + 1 out_{x,y}\to in_{x,y+1} outx,y→inx,y+1,费用为 0 0 0,容量为 ∞ \infty ∞ 的边,表示往下或者往右走。
跑最大费用最大流即可。
例题二:P3358。
首先发现一个开区间对其内部的点都有相同的覆盖次数贡献,所以可以对区间的两个端点离散化。
首先可以想到将数轴上的点用费用为 0 0 0,容量为 k k k 的边连成一条链,如何刻画当前点的覆盖次数是现在的首要问题。
设流入当前点的流量为 f f f,那么考虑构造一个网格,使得 k − f k-f k−f 成为这个点的覆盖次数。明显对于线段 i i i,连一条 l i → r i l_i\to r_i li→ri,容量为 1 1 1,费用为其长度的边可以符合条件。
建立超级源点和超级汇点跑最大费用最大流即可。
例题三:CF277E。
发现一个点选父亲和这个点选儿子是互相独立的,所以考虑拆点, i n x in_x inx 表示这个点去选儿子, o u t x out_x outx 表示这个点的父亲被选好了。
建立超级源点 S S S, S S S 向每个 i n x in_x inx 连一条费用为 0 0 0,容量为 2 2 2 的边,表示最多选两个儿子。建立超级汇点 T T T,每个 o u t x out_x outx 向 T T T 连一条费用为 0 0 0,容量为 1 1 1 的边,表示这个点的父亲选完了,最多一个父亲。
如果两个点 x , y x,y x,y 满足 x x x 可以成为 y y y 的父亲,连一条 i n x → o u t y in_x\to out_y inx→outy ,容量为 1 1 1,费用为两点距离的边,这样最终的费用就是此树的权值。
跑最大费用最大流即可,值得注意的是如果最终的最大流不是 n − 1 n-1 n−1,就无解,因为除了根节点之外,每个点都得有父亲。
例题四:CF863F。
首先可以暴力预处理出每个点的最终范围 l i ∼ r i l_i\sim r_i li∼ri,显然如果 l i > r i l_i>r_i li>ri 无解,否则必然有解。
注意到 x 2 = 1 + 3 + ⋯ + ( 2 x − 1 ) x^2=1+3+\cdots+(2x-1) x2=1+3+⋯+(2x−1)。将每种权值拆分为两个点 i n i in_i ini 和 o u t i out_i outi,分别连上容量为 1 1 1,费用为 1 , 3 , ⋯ , 2 n − 1 1,3,\cdots,2n-1 1,3,⋯,2n−1 的边,如果有 k k k 个流量流入了 i n i in_i ini,相当于有 k k k 个数的值为 i i i,跑最小费用最大流最终的费用刚好是 k 2 k^2 k2,符合题目所求。
最后的建图就十分显然了,建立超级源点 S S S, S S S 向每个 i i i 连容量为 1 1 1,费用为 0 0 0 的边,表示每个 a i a_i ai 只能选一个数,每个 i i i 向 i n l i ∼ r i in_{l_i\sim r_i} inli∼ri 连容量为 1 1 1,费用为 0 0 0 的边,表示 a i a_i ai 可以选这个数。建立超级汇点 T T T,每个 o u t i out_i outi 向 T T T 连容量为 ∞ \infty ∞,费用为 0 0 0 的边。
然后跑最小费用最大流即可。
放一道不错的练习题:P4249以及其双倍经验CF1264E。
Part.4 最小割及其应用
定义一张图的割为一种点的划分方式:将图分为 s , t s,t s,t 两个集合,其中 S ∈ s , T ∈ t S\in s,T\in t S∈s,T∈t。割的权值为 ∑ u ∈ s , v ∈ t c u , v \sum\limits_{u\in s,v\in t} c_{u,v} u∈s,v∈t∑cu,v。最小割就是权值最小的割。
最小割的几个性质:
- 最小割和最大流是相等的,即最大流最小割定理;
- 最小割的割边一定是满流边;
- 若删掉或减小某条满流边的容量后最大流不减小,则该边一定不是最小割中的边;
- 网络流的任意一条增广路至少经过一条最小割边;
- 对于某满流边,如果在残留网络中,源点能到达该边一端点,另一端点能到达汇点,则该满流边就是关键割边(即一定是最小割的一个割边)。
例题一:最大权闭合子图。
给出一张有向图 G = ( V , E ) G=(V,E) G=(V,E),每个点有一个权值 w i w_i wi。现在要选出一个点集 V ′ V' V′,满足对于任意一条边 x → y x\to y x→y,如果 x ∈ V ′ x\in V' x∈V′,则 y ∈ V ′ y\in V' y∈V′。你需要让 ∑ x ∈ V ′ w x \sum\limits_{x\in V'}w_x x∈V′∑wx 最大。
首先考虑最理想的情况,即所有 w i > 0 w_i>0 wi>0 的点都被选,问题就变成了最小代价(删掉正点权或加入负点权)。所以考虑用最小割求解,这是一种非常常见的套路。
设在一组割中与 S S S 相连的点就是选,否则就是不选。对于 w i > 0 w_i>0 wi>0 的点,连边 S → i S\to i S→i,容量为 w i w_i wi,对于 w i < 0 w_i<0 wi<0 的点,连边 i → T i\to T i→T,容量为 − w i -w_i −wi。
对于在原图中的一条边 x → y x\to y x→y,连一条 x → y x\to y x→y,容量为正无穷的边。这样就能保证如果 x x x 在集合 s s s 里, y y y 肯定在集合 s s s 里,否则这组割的权值就会加上 ∞ \infty ∞,肯定不是最小的。
跑出来的最大流就是最小的代价,这样答案就能被轻松求出。
例题二:P2057。
考虑用最小割求解,在一组割中,与 S S S 相连就是投赞成票,否则就是投反对票。
首先如果 i i i 意愿赞成,就与 S S S 连一条容量为 1 1 1 的边,否则就向 T T T 连一条容量为 1 1 1 的边。这样如果这条边是割边,就会因与自己意愿相反对割贡献 1 1 1 的权值。
对于一个朋友关系 ( x , y ) (x,y) (x,y),连两条 x → y , y → x x\to y,y\to x x→y,y→x 容量为 1 1 1 的边,如果两个点不在同一集合内,其中一条边会对割贡献 1 1 1 的权值。
跑最小割即可。
练习题:P4313。
Part.5 上下界网络流及其应用
首先什么是上下界网络流,就是每条边除了流出网络的上限 c u , v c_{u,v} cu,v(即容量),新增了一个性质:下界 b u , v b_{u,v} bu,v,要求 b u , v ≤ f u , v ≤ c u , v b_{u,v}\le f_{u,v}\le c_{u,v} bu,v≤fu,v≤cu,v。
首先是无源汇上下界可行流。
什么是无源汇,相当于网络中的每个点都满足流量守恒。让你求出满足这些条件的一种流法。
首先先考虑去掉下界的限制,即每条边先流出 b u , v b_{u,v} bu,v 的流量。但这样明显会不符合流量守恒,考虑建一张图去调整。
设 i n u = ∑ ( v , u ) ∈ E b v , u , o u t u = ∑ ( u , v ) ∈ E b u , v in_u=\sum\limits_{(v,u)\in E} b_{v,u},out_u=\sum\limits_{(u,v)\in E} b_{u,v} inu=(v,u)∈E∑bv,u,outu=(u,v)∈E∑bu,v。如果 i n u > o u t u in_u>out_u inu>outu,说明还需要流出 i n u − o u t u in_u-out_u inu−outu 的流量,否则还需要流入 o u t u − i n u out_u-in_u outu−inu 的流量。
考虑建图求解,对于 i n u > o u t u in_u>out_u inu>outu,连一条 S → u S\to u S→u,容量为 i n u − o u t u in_u-out_u inu−outu 的边;如果 o u t u > i n u out_u>in_u outu>inu 连一条 u → T u\to T u→T,容量为 o u t u − i n u out_u-in_u outu−inu 的边。对于 ( u , v ) ∈ E (u,v)\in E (u,v)∈E,连一条 u → v u\to v u→v,容量为 c u , v − b u , v c_{u,v}-b_{u,v} cu,v−bu,v。对于这个图跑网络流最大流,如果所有与 S S S 相连的边都是满流,原图就有可行流(实际流量为图中这条边的流量加上 b u , v b_{u,v} bu,v),否则无解。
加上费用也很简单,用 ∑ ( u , v ) ∈ E b u , v × w u , v \sum\limits_{(u,v)\in E} b_{u,v}\times w_{u,v} (u,v)∈E∑bu,v×wu,v 加上最小费用最大流即可。
那如何求有源汇上下界可行流/最大流/最小流呢?
第一个直接转化为无源汇上下界可行流,连一条 T → S T\to S T→S,下界为 0 0 0,上界为 ∞ \infty ∞ 即可。跑出一组可行流后,此时的真实流量是 T → S T\to S T→S 的流量,记为 f 1 f_1 f1。
那如何求最大流呢?
考虑调整法。还记得求可行流时建的图吗,其实我们只需要拆掉 T → S T\to S T→S 的边,然后再跑一遍 S → T S\to T S→T 的最大流,记为 f 2 f_2 f2。两者相加就行了。
同样的去求最小流,发现跑 T → S T\to S T→S 的最大流相当于退流,记为 f 2 f_2 f2,两者相减就行了。
这里放一下求最大流的模板的代码:
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e2+5,M = 1e4+N;
int n,m,s,t,ss,tt,a[N],cnt = 1,head[N],to[M<<1],nxt[M<<1],g[M<<1];
inline void add(int x,int y,int z)
{nxt[++cnt] = head[x];head[x] = cnt;to[cnt] = y,g[cnt] = z;nxt[++cnt] = head[y];head[y] = cnt;to[cnt] = x,g[cnt] = 0;
}
int dis[N],now[N];
inline bool bfs()
{for(int i = 0;i<=n+1;i++) dis[i] = -1;queue<int> q;q.push(s);dis[s] = 0;now[s] = head[s];while(!q.empty()){int u = q.front();q.pop();for(int i = head[u];i;i = nxt[i]){int v = to[i];if(g[i]>0&&dis[v]==-1){q.push(v);dis[v] = dis[u]+1,now[v] = head[v];if(v==t) return 1;}}}return 0;
}
int dfs(int u,int s)
{if(u==t) return s;int k,res = 0;for(int i = now[u];i;i = nxt[i]){if(!s) break; int v = to[i];now[u] = i;if(g[i]>0&&dis[v]==dis[u]+1){k = dfs(v,min(s,g[i]));if(k==0) dis[v] = 2e9;g[i]-=k,g[i^1]+=k,res+=k,s-=k;} }return res;
}
inline int dinic()
{int res = 0;while(bfs()) res+=dfs(s,2e18);return res;
}
signed main()
{ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);cin>>n>>m>>ss>>tt;s = 0,t = n+1; for(int i = 1,u,v,c,b;i<=m;i++){cin>>u>>v>>b>>c;add(u,v,c-b);a[u]-=b,a[v]+=b;}int mx = 0;for(int i = 1;i<=n;i++)if(a[i]>0) add(s,i,a[i]),mx+=a[i];else if(a[i]<0) add(i,t,-a[i]);add(tt,ss,1e18);if(dinic()<mx) return cout<<"please go home to sleep",0;mx = g[cnt];g[cnt] = g[cnt-1] = 0;s = ss,t = tt;cout<<mx+dinic();return 0;
}
例题一:P4843。
建立超级源点 S S S,以及超级汇点 T T T。 S S S 向每个 i i i 建立下界为 0 0 0,上界为 ∞ \infty ∞ 的边,每个 i i i 向 T T T 连一条下界为 0 0 0,上界为 ∞ \infty ∞ 的边。由于原图中的每条边 ( u , v ) (u,v) (u,v) 都得覆盖一次,于是连一条 u → v u\to v u→v,下界为 1 1 1,上界为 ∞ \infty ∞ 的边。
跑有源汇上下界最小流即可。
例题二:P5192。
对于每天和每个少女飞别建一个点,建超级源点 S S S,以及超级汇点 T T T。每个少女 x x x 向汇点连下界为 G x G_x Gx,上界为 ∞ \infty ∞ 的边,源点向第 x x x 天连一条下界为 0 0 0,上界为 D x D_x Dx 的边。第 x x x 天向第 y y y 个拍照的少女 T x , y T_{x,y} Tx,y 连一条下界为 L x , y L_{x,y} Lx,y,上界为 R x , y R_{x,y} Rx,y 的边。
跑有源汇上下界最大流即可。
Part.6 二分图
一张图是二分图当且仅当这张图可以分为两个不相交的点集 A , B A,B A,B,即 A ∪ B = V , A ∩ B = ∅ A\cup B=V,A\cap B=\emptyset A∪B=V,A∩B=∅,满足不存在 ( u , v ) ∈ E (u,v)\in E (u,v)∈E 使得 u ∈ A , v ∈ A u\in A,v\in A u∈A,v∈A 或者 u ∈ B , v ∈ B u\in B,v\in B u∈B,v∈B。
一个图是二分图的充分必要条件是这张图里面没有奇环。当然也可以用黑白染色来判断一个图是否是二分图。
二分图的边覆盖/匹配/点覆盖/独立集问题
对于一张图,有如下定义:
- 边覆盖: G G G 的一个边集 E ′ ⊆ E E'\subseteq E E′⊆E 满足其是 G G G 的边覆盖,当且仅当对于每个点 x ∈ V x\in V x∈V,存在另一个点 y y y 使得 ( x , y ) ∈ E ′ (x,y)\in E' (x,y)∈E′,即每个点至少是 E ′ E' E′ 中一条边的端点;
- 匹配: G G G 的一个边集 E ′ ⊆ E E'\subseteq E E′⊆E 满足其是 G G G 的匹配,当且仅当 E ′ E' E′ 中任意两条边不存在共同点;
- 点覆盖: G G G 的一个点集 V ′ ⊆ V V'\subseteq V V′⊆V 是 G G G 的点覆盖当且仅当对于图中每条边,其端点至少有一个属于 V ′ V' V′;
- 独立集: G G G 的一个点集 V ′ ⊆ V V'\subseteq V V′⊆V 是 G G G 的点覆盖当且仅当对于图中每条边,其端点至多有一个属于 V ′ V' V′;
事实上,对于任意一张图 G = ( V , E ) G=(V,E) G=(V,E),有:
- 如果 G G G 存在边覆盖,则最小边覆盖加上最大匹配等于 ∣ V ∣ |V| ∣V∣。
证明:首先构造出一个最大匹配 E ′ E' E′,记其端点组成的集合大小为 c c c,明显 c = 2 ∣ E ′ ∣ c=2|E'| c=2∣E′∣,现在考虑加边是其变为最小边覆盖,而每加一条边 c c c 会加一,最后我们需要将 c c c 变为 ∣ V ∣ |V| ∣V∣,所以加边个数为 ∣ V ∣ − 2 ∣ E ′ ∣ |V|-2|E'| ∣V∣−2∣E′∣,最小边覆盖为 ∣ V ∣ − 2 ∣ E ′ ∣ + ∣ E ′ ∣ = ∣ V ∣ − ∣ E ′ ∣ |V|-2|E'|+|E'|=|V|-|E'| ∣V∣−2∣E′∣+∣E′∣=∣V∣−∣E′∣,得证。 - G G G 的最大独立集加上最小点覆盖大小刚好为 ∣ V ∣ |V| ∣V∣。
证明:对于 G G G 的最大独立集 V ′ V' V′,显然每条边最多只有一个端点属于 V ′ V' V′。令 V ′ ′ = V − V ′ V''=V-V' V′′=V−V′,则每条边至少一个端点属于 V ′ ′ V'' V′′,这刚好与点覆盖的定义相同,即 V ′ ′ V'' V′′ 就是这张图的最小点覆盖。
二分图最大匹配模板。
建立超级源点 S S S 以及超级汇点 T T T,将原图中的每个点 u u u 拆分为两个点 i n u , o u t u in_u,out_u inu,outu, S S S 向每个 i n u in_u inu 连容量为 1 1 1 的边( u u u 最多匹配一个点),每个 o u t u out_u outu 向 T T T 连容量为 1 1 1 的边( u u u 最多被一个点匹配)。对于每个可能的匹配 ( u , v ) (u,v) (u,v),连 i n u → o u t v in_u\to out_v inu→outv 且容量为 1 1 1 的边。这样建出来的图跑最大流即为答案。
二分图有一个性质就是最大匹配等于最小点覆盖,虽然笔者不会证明,但是这意味着求出了二分图最大匹配就相当于求出了另外三个的值。
接下来就是例题。
例题一:P4251。
这种每行选一个使得没有重复的列的题都有一个套路:将行和列连边,然后跑二分图最大匹配。
首先想到二分答案,我们需要判断答案是否小于等于 x x x。将所有小于等于 x x x 的数置为可以选的,容易用二分图最大匹配求出当前能最多选几个数,最后判断是否大于等于 n − k + 1 n-k+1 n−k+1 个数即可。
例题二:P2764。
首先当每个点自成一条路径的时候,当前的路径条数为 n n n,可以考虑一条边 ( x , y ) ∈ E (x,y)\in E (x,y)∈E,满足覆盖 x x x 的链没有后继,覆盖 y y y 的链没有前驱。将这两条链合并起来,自然会使路径数减一。
将每个点拆分为两个点 a x , b x a_x,b_x ax,bx, a x a_x ax 被选相当于 x x x 有了后继, b x b_x bx 被选相当于 x x x 有了前驱,发现每条边 ( x , y ) (x,y) (x,y) 相当于将 a x a_x ax 和 b y b_y by 匹配起来,这就是典型的二分图最大匹配了。
现在的问题在于如何输出方案。不难发现在图中流往哪个边流相当于这条边两个端点在同一条链里面,这样记录方案也非常简单了。
练习题:CF1592F2。
Hall 定理
注:此知识点几乎和网络流没有关系,仅为对二分图的补充,读者可以选择性阅读。
Hall 定理:对于一张二分图 G = ( V 1 , V 2 , E ) G=(V_1,V_2,E) G=(V1,V2,E),定义函数 f ( V ) ( V ∈ V 1 ) f(V)(V\in V_1) f(V)(V∈V1) 为与点集 V V V 中相邻点的并集,那么二分图有完美匹配的充要条件为 ∀ V ⊆ V 1 , ∣ f ( V ) ∣ ≥ ∣ V ∣ \forall V\subseteq V_1,|f(V)|\ge |V| ∀V⊆V1,∣f(V)∣≥∣V∣。笔者暂时不会证明。
练习题:CF338E。
相关文章:
网络流学习笔记
注:笔者是蒟蒻,所以本文几乎是干货,枯燥无味甚至可能会引人不适,请读者谨慎阅读。 为了笔者快爆掉的肝点个赞好吗??? Part.1 网络流基础定义 一个有向带权图 G ( V , E ) G(V,E) G(V,E) 是…...
Mybatis PLUS查询对List使用OR模糊查询
Mybatis PLUS查询对List使用OR模糊查询 1、版本2、代码3、效果 1、版本 Mybatis PLUS版本:3.5.7 注意:版本3.1.2及以下是需要return的 因当前为高版本,代码中已将 return 注释。 2、代码 QueryWrapper<Object> queryWrapper new Que…...
Debezium日常分享系列之:Debezium Engine
Debezium日常分享系列之:Debezium Engine 依赖打包项目在代码中输出消息格式消息转换消息转换谓词高级记录使用引擎属性异步引擎属性数据库模式历史属性处理故障 Debezium连接器通常通过部署到Kafka Connect服务来运行,并配置一个或多个连接器来监视上游…...
I.MX6U 裸机开发20. DDR3 内存知识
I.MX6U 裸机开发20. DDR3 内存知识 一、DDR3内存简介1. DDR发展历程SRAMSDRAMDDR1DDR2DDR3DDR4DDR5 2. 开发板资源3. DDR3的时间参数1. 传输速率2. tRCD3. CL 参数作用取值范围工作原理4. tRC参数原理单位与取值5. tRAS重要性及作用 二、I.MX6U MMDC 控制器1. MMDC简介…...
【R安装】VSCODE安装及R语言环境配置
目录 VSCODE下载及安装VSCODE上配置R语言环境参考 Visual Studio Code(简称“VSCode” )是Microsoft在2015年4月30日Build开发者大会上正式宣布一个运行于 Mac OS X、Windows和 Linux 之上的,针对于编写现代Web和云应用的跨平台源代码编辑器&…...
ES更新问题 Failed to close the XContentBuilder异常
问题描述 使用RestHighLevelClient对文档进行局部更新的时候报错如下: Suppressed: java.lang.IllegalStateException: Failed to close the XContentBuilderat org.elasticsearch.common.xcontent.XContentBuilder.close(XContentBuilder.java:1011)at org.elast…...
svn-git下载
windows: svn 客户端:-------------- TortoiseSVN 安装 下载地址:https://tortoisesvn.net/downloads.html, 页面里有语言包补丁的下载链接。 目前最新版为 1.11.0 下载地址: https://osdn.net/projects/tortoisesvn/storage/1.…...
10个Word自动化办公脚本
在日常工作和学习中,我们常常需要处理Word文档(.docx)。 Python提供了强大的库,如python-docx,使我们能够轻松地进行文档创建、编辑和格式化等操作。本文将分享10个使用Python编写的Word自动化脚本,帮助新…...
Paddle Inference部署推理(十八)
十八:Paddle Inference推理 (C)API详解 3. 使用 CPU 进行预测 注意: 在 CPU 型号允许的情况下,进行预测库下载或编译试尽量使用带 AVX 和 MKL 的版本 可以尝试使用 Intel 的 MKLDNN 进行 CPU 预测加速,默…...
Redis开发02:redis.windows-service.conf 默认配置文件解析与注解
文件位置:redis安装目录下的 redis.windows-service.conf ,存放了redis服务的相关配置,下面列举出默认配置的含义: 配置项含义bind 127.0.0.1限制 Redis 只监听本地回环地址,意味着只能从本地连接 Redis。protected-m…...
redis大key和热key
redis中大key、热key 什么是大key大key可能产生的原因大key可能会造成什么影响如何检测大key如何优化删除大key时可能的问题删除大key的策略 热key热key可能导致的问题解决热key的方法 什么是大key 大key通常是指占用内存空间过大或包含大量元素的键值对。 数据量大ÿ…...
Dubbo 最基础的 RPC 应用(使用 ZooKeeper)
看国内的一些项目时 Dubbo 这个词经常闪现,一直也不以为然,未作搜索,当然也不知道它是做什么用的。直到最近阅读关于大型网站架构相关的书中反复提到 Dubbo 后,觉得不能再对它视而不见。Google 了一下,它是在阿里巴巴创…...
科技赋能:企业如何通过新技术提升竞争力的策略与实践
引言 在当今瞬息万变的商业环境中,科技的迅猛发展正在重新定义行业的游戏规则。无论是小型企业还是跨国巨头,都感受到数字化转型的迫切需求。过去,企业竞争力更多依赖于成本控制、资源调配或市场覆盖,而如今,新技术的引…...
从0开始深度学习(33)——循环神经网络的简洁实现
本章使用Pytorch的API实现RNN上的语言模型训练 0 导入库 import torch import torch.nn as nn import torch.nn.functional as F from torch.utils.data import Dataset, DataLoader from collections import Counter import re import math from tqdm import tqdm1 准备数据 …...
【FAQ】HarmonyOS SDK 闭源开放能力 — 公共模块
1.问题描述: 文档哪里能找到所有的权限查看该权限是用户级的还是系统级的。 解决方案: 您好,可以看一下下方链接是否可以解决问题: https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/permissions-for-all-V…...
百度 文心一言 vs 阿里 通义千问 哪个好?
背景介绍: 在当前的人工智能领域,随着大模型技术的快速发展,市场上涌现出了众多的大规模语言模型。然而,由于缺乏统一且权威的评估标准,很多关于这些模型能力的文章往往基于主观测试或自行设定的排行榜来评价模型性能…...
内网不出网上线cs
一:本地正向代理目标 如下,本地(10.211.55.2)挂好了基于 reGeorg 的 http 正向代理。代理为: Socks5 10.211.55.2 1080python2 reGeorgSocksProxy.py -l 0.0.0.0 -p 1080 -u http://10.211.55.3:8080/shiro/tunnel.jsp 二:虚拟机配置proxifer 我们是…...
ubuntu22开机自动登陆和开机自动运行google浏览器自动打开网页
一、开机自动登陆 1、打开settings->点击Users 重启系统即可自动登陆桌面 二、开机自动运行google浏览器自动打开网页 1、安装google浏览器 sudo wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb sudo dpkg -i ./google-chrome-stable…...
企业建站高性能的内容管理系统
AnQiCMS 是一款高性能的内容管理系统,基于Go语言开发。它支持多站点、多语言管理,提供灵活的内容发布和模板管理功能,同时,系统内置丰富的利于SEO操作的功能,支持包括自定义字段、文档分类、批量导入导出等功能 AnQiC…...
【爬虫框架:feapder,管理系统 feaplat】
github:https://github.com/Boris-code/feapder 爬虫管理系统 feaplat:http://feapder.com/#/feapder_platform/feaplat 爬虫在线工具库 :http://www.spidertools.cn :https://www.kgtools.cn/1、feapder 简介 对于学习 Python…...
faiss库中ivf-sq(ScalarQuantizer,标量量化)代码解读-5
训练过程 通过gdb调试得到这个ivfsq的训练过程,我尝试对这个内容具体训练过程进行解析,对每个调用栈里面的逻辑和代码进行解读。 步骤函数名称调用位置说明1faiss::IndexIVF::train/faiss/IndexIVF.cpp:1143开始训练,判断是否需要训练第一级…...
代码随想录算法训练营第六十天|Day60 图论
Bellman_ford 队列优化算法(又名SPFA) https://www.programmercarl.com/kamacoder/0094.%E5%9F%8E%E5%B8%82%E9%97%B4%E8%B4%A7%E7%89%A9%E8%BF%90%E8%BE%93I-SPFA.html 本题我们来系统讲解 Bellman_ford 队列优化算法 ,也叫SPFA算法…...
在嵌入式Linux下如何用QT开发UI
在嵌入式 Linux 环境下使用 Qt 开发用户界面 (UI) 是一个常见的选择。Qt 提供了丰富的功能、跨平台支持以及优秀的图形界面开发能力,非常适合用于嵌入式系统。以下是开发流程的详细步骤: 1. 准备开发环境 硬件环境 一块运行嵌入式 Linux 的开发板&…...
【JavaScript】Promise详解
Promise 是 JavaScript 中处理异步操作的一种强大机制。它提供了一种更清晰、更可控的方式来处理异步代码,避免了回调地狱(callback hell)和复杂的错误处理。 基本概念 状态: Pending:初始状态,既不是成功…...
1062 Talent and Virtue
About 900 years ago, a Chinese philosopher Sima Guang wrote a history book in which he talked about peoples talent and virtue. According to his theory, a man being outstanding in both talent and virtue must be a "sage(圣人)"…...
C++《二叉搜索树》
在初阶数据结构中我学习了树基础的概念以及了解了顺序结构的二叉树——堆和链式结构二叉树该如何实现,那么接下来我们将进一步的学习二叉树,在此会先后学习到二叉搜索树、AVL树、红黑树;通过这些的学习将让我们更易于理解后面set、map、哈希等…...
机器学习-神经网络(BP神经网络前向和反向传播推导)
1.1 神经元模型 神经网络(neural networks)方面的研究很早就已出现,今天“神经网络”已是一个相当大的、多学科交叉的学科领域.各相关学科对神经网络的定义多种多样,本书采用目前使用得最广泛的一种,即“神经网络是由具有适应性的简单单元组成的广泛并行互连的网络,它的组织能够…...
基于智能物联网关的车辆超重AI检测应用
超重超载是严重的交通违法行为,超重超载车辆的交通安全风险极高,像是一颗行走的“不定时炸弹”,威胁着社会公众的安全。但总有一些人受到利益驱使,使超重超载的违法违规行为时有发生。 随着物联网和AI技术的发展,针对预…...
记录pbootcms提示:登录失败:表单提交校验失败,请刷新后重试的解决办法
问题描述 pbootcms后台登录的时候提示“登录失败:表单提交校验失败,请刷新后重试!” 解决办法 删除runtime目录,或尝试切换PHP版本,选择7.3或5.6一般就能解决了。...
【JavaScript】同步异步详解
同步和异步是编程中处理任务执行顺序的两种不同方式。理解这两种概念对于编写高效和响应式的应用程序至关重要。 同步(Synchronous) 定义:同步操作是指一个任务必须在下一个任务开始之前完成。换句话说,代码按顺序执行ÿ…...
六盘水网站建设/铜仁搜狗推广
Mock an function to modify partial return value by special arguments on Python python mock一个带参数的方法,修改指定参数的返回值,大家直接看代码就能懂。 want mock code: import requestsdef get_value(arg):resp requests.get(https://httpbi…...
wordpress电商平台插件/站长工具seo综合查询怎么使用的
数据类型 Java虚拟机中,数据类型可以分为两类:基本类型和引用类型。基本类型的变量保存原始值,即:他代表的值就是数值本身;而引用类型的变量保存引用值。“引用值”代表了某个对象的引用,而不是对象本身&am…...
手机网站建设的趋势/常见的网络营销方法有哪些
1. HashTable和HashMap的区别 参考博客https://www.cnblogs.com/williamjie/p/9099141.html HashTable和HashMap都是基于哈希表实现的,每一个元素都是一个键值对,内部通过单链表解决冲突问题 1.1 从继承类来看 Hashtable继承自Dictionary类ÿ…...
wordpress网站标签logo/网站推广技巧
手机屏幕的分辨率 320*480 ,图像尺寸72*72,正常显示。 在 240*320 的屏幕分辨率下,图像尺寸是多少(缩放比率是多少)才会正常显示(不失真,不模糊)? 一般比你手机屏幕大的都…...
常州网站建设费用/seo范畴
Android Studio集成和加入了一些有用的工具。当中一个便是terminal。在Windows平台下Android Studio中的terminal在原理上实际使用的是window中的cmd控制台也就是位于C:\Windows\System32\文件夹下的cmd.exe。升级了win10的用户会发现,win10下的cmd比曾经平台下的cm…...
怎么制作个人作品网站/免费顶级域名注册
2019独角兽企业重金招聘Python工程师标准>>> 老司机的思路: 先生成40个学生对象 放在list里 产生随机数 判断随机和学生Id是否一样 如果一样给他们设置是否到课和点名标记为TRUE 最后一个for循环 判断是不是都点过名了顺便记录到课人数 实现代码&am…...