标签:匹配 int 短路 edge ##### now 233
$upd:$ $2020.02.29$ 将(大部分)所有图论所涉及知识、模型写了上来;仅限于学了、学了没写题、写了没总结、学了没看懂四种情况;时间有限没有写完,计划明天补坑; $upd:2020.03.01$ 完善了部分二分图的总结 $upd:2020.03.02$ 补充网络流部分,按照abs博客中的等级:$level 1->level2^-$ $upd:2020.03.10$ 完善了关于网络流的一些奇奇怪怪的东西 $upd$:$2020.03.30$ 完善了各种东西,嗯变成自己的知识体系 ### 图论学习总结 #### 最短路 ##### $Dijkstra:$ 类似于$pirm$,是一种基于贪心的最短路算法,通过源点向外扩展; 不可求最长路,不能处理负权; 可以进行堆优化; ##### $Bellman-ford / SPFA: $ 通过不断松弛以达到最短路;SPFA是队列优化版本,可以进行堆优化; 可以处理负权,负环没有最短路,可以求最长路;应用于差分约束; 会被毒瘤出题人卡掉(网格图);尽量不用; ##### $Floyd:$ 全源最短路;基于DP的最短路算法;一般可以处理$n=300$; 状态转移方程$dis[i] [j]=dis[i] [k]+dis[k] [j] $表示从$k$点中转; 可以用来求二分图的传递闭包; ##### $Johnson:$ 全源最短路的效率极限就是$n$遍单源最短路; 但是SPFA容易被卡,所以有负权的时候用$Johnson$算法; 做法是建立超级源点$S$,连每个点$add(S,i,0)$;以$S$为单源,先求出最短路; 对于每一条边$(u,v,w)$,重新建立一条新的边,权为$w−dis[v]+dis[u]$; 再以每一个点为单源,跑$Dijkstra$就可以了; 实际上,这个新求出的最短路相比以前,只有在数值上有一个$dis[v]-dis[u]$的常量偏差, 而最优的路线没有改变; ------ ##### 关于$Johnson$的证明: $1.$边权非负 若有$(x,y,z)$,则$dis[y]−dis[x]<=z$得$z−(dis[y]−dis[x])>=0$;可知新的边权非负。 $2.$最短路不变 若有路径$x1−>x2−>x3…−>xn$;原路径为$z1+z2+…z[n−1]$ 则新路径为$(z1−(f[x2]−f[x1)])+(z2−(f[x3]−f[x2]))+…+(z[n−1]−(f[xn]−f[xn−1])) $ 原式$=z1+z2+…z[n−1]−f[xn]+f[x1] $,为关于起点终点的常数,所以最短路不变。 ------ ##### 差分约束 形似$dis[i]<=dis[j]+w$的式子可以通过求解最短路来获得多个类似的不等式组的解集; 建立超级源点$S$,向所有点连接$0$的边 $dis[i]$就表示第$i$个元素$k_i<=dis[i]$(默认最小值$0$) ------ ##### $k$短路 使用$A^*$实现;(应该还有一种更好的办法求$k$短路) $unDone$ ------ ##### 最小瓶颈生成树 $unDone$ ------ ##### 最小乘积生成树 $unDone$ ------ ##### 次小生成树 考虑每一条不在最小生成树上的边;连上这条边必然出现一个环; 在这个环上找一条最大的边,替换,就得到了$delta$最小的一个次小生成树; 可以通过倍增记录两点之间的链上的最大值; 当所求次小生成树为严格的时,还要记录次小值; ------ ##### 最小树形图 $unDone$ ------ ##### 负环 因为存在负环即不存在最短路,所以在$SPFA$的过程中一直不会退出; 记录每个点的松弛次数,如果松弛次数达到$n$次,说明一定存在负环; ------ ##### 最小环 $floyd$求最小环 $Dijkstra$求最小环 二进制分组的优化 可以优化到log ------ ##### 生成树/最短路计数 $unDone$ ------ ##### $2-SAT$问题 可以用扩展域并查集求解; $unDone$ ##### $Tarjan$算法 割点 去掉该点使得 $unDone$ 割边(桥) $unDone$ 有向图强联通分量 $unDone$ 无向图点双联通分量 $unDone$ 无向图边双联通分量 将图中的桥全部去掉,所剩下的 ##### $Kosaraju$算法 用于求有向图强联通分量; $unDone$ ##### 匈牙利算法 用途:计算二分图的最大匹配;每成功一次,说明最大匹配的含边数加$1$; 即每次寻找增广路,直到没有增广路为止; ##### 二分图相关概念及求法 ##### 最大匹配 二分图中包含边数最多的,任意两条边都没有公共顶点的边的集合; ##### 增广路 起点和终点都是非匹配点的一条路径,使得非匹配边和匹配边交替出现;长度一定是奇数;奇数边一定是非匹配边;偶数边一定是匹配边; ##### 完备匹配 二分图左右均为$N$个节点,且最大匹配包含$N$条匹配边,则该二分图含有完备匹配; ##### 多重匹配 一个二分图左部分有$N$个节点,右部分有$M$个节点,对于节点$N_i$,最多使得$kl_i$条边与其相连;对于节点$M_i$,最多使得$kr_i$条边与其相连;求最大匹配; 解决方案:1.拆点;对于节点$N_i$,将这个点拆成$kl_i$点,这$kl_i$个点每个点与原$N_i$点与右部分连的边都要连边;2.当该图只有一边是多重的时,可以将多重的一边放到左部分,对每个左部分节点$N_i$都进行$kl_i$次增广路的寻找;4.网络流 ##### 交错树 从某个点开始寻找增广路的匹配失败以后按$dfs$序形成的树;根为左部分节点,叶子也为左部分节点;奇数层的边都是非匹配边;偶数层的边都是匹配边; ##### 顶标 满足$\forall i,j$,$A_i+B_j>=w(i,j)$的顶点标记值; ##### 最小点覆盖 使得二分图任意一条边都至少有一个端点在集合$S$内的最小点集$S$; ##### 最大独立集 满足点集内任意两点之间没有边相连的最大点集$S$ ##### 最大团 满足点集内任意两点之间至少有一条边相连的最大点集$S$ continue... ##### 二分图相关定理 图是二分图,当且仅当无奇环; 一组匹配是最大匹配$\Leftrightarrow$该匹配无增广路 $'0'$要素:节点能分成两个集合,集合内无边 $’1'$要素:每个节点只能与一条匹配边相连 多重匹配可以通过拆点(或网络流)来求;当其中一边的l=1的时候,可以特殊化匈牙利,不用真的求最大匹配 $KM$只能求带权最大匹配一定是完备匹配的情况 相等子图中存在完备匹配,则此完备匹配就是此图带权最大匹配 二分图最小点覆盖包含点数等于最大匹配包含边数(可以先证$a<=b$再$a>=b$得$a==b$) $’2'$要素:求最小点覆盖的时候,每条边有两个端点,至少选一个(这个时候偏重边而不是点,事件变成了边) 无向图的最大团等于其补图的最大独立集 二分图的最大独立集大小等于节点总数减最大匹配数 $continue...$ ------ $Hopcroft-Karp$算法 ------ ##### $KM$算法 用来求一个二分图的带权最大匹配;即在满足最大匹配的前提下,最大化边权之和; 前提:必须是带权最大匹配一定是完备匹配的时候;一般情况下使用费用流; 在满足$\forall i,j$,$A_i+B_j>=w(i,j)$的前提下,任意赋值顶标;一般使得$A_i=max_{1<=j<=n}{w(i,j)}$,$B_i=0$ 再不断扩大相等子图的规模,直到相等子图有完备匹配; ```cpp int w[110][110]; int la[110],lb[110];//顶标 int va[110],vb[110];//标记 int match[110];//匹配 int n,delta,upd[110];//更新 int dfs(int x) { va[x]=1; for(int i=1;i<=n;i++) if(!vb[i]) if(la[x]+lb[i]==w[x][i]) { vb[i]=1; if(!match[i]||dfs(match[i])) { match[i]=x; return 1; } } else upd[i]=min(upd[i],la[x]+lb[i]-w[x][i]);//double记得设定eps return 0; } int KM() { memset(la,-INF,sizeof la); memset(lb,0,sizeof lb); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) la[i]=max(la[i],w[i][j]); for(int i=1;i<=n;i++) while(1) { memset(va,0,sizeof va); memset(vb,0,sizeof vb); memset(upd,INF,sizeof upd); delta=INF; if(dfs(i))break; for(int j=1;j<=n;j++) if(!vb[j])delta=min(min,upd[j]);//去到过,就更新 for(int j=1;j<=n;j++) { if(va[j])la[j]-=delta; if(vb[j])lb[j]+=delta; } } int ans=0;//求权值和 for(int i=1;i<=n;i++)ans+=w[match[i]][i]; return ans; } ``` ------ ##### 分数规划 最优比例环 最优比例生成树 ------ ### 网络流学习总结 ##### 相关概念 ##### 源点和汇点 只有出度和只有入度的点 ##### 流量 定义在二元组$(x,y)$上的实数函数$f(x,y)$,并满足$f(x,y)=②-f(y,x)<=①c(x,y)$, $③\forall x!=S,T$,$\Sigma_{(u,x)\in E}f(u,x)=\Sigma_{(x,v)\in E}f(x,v)$; 即非源汇点的所有点不储存流,流进多少流出多少;$f(x,y)$被称为流函数$(?)$ ##### 性质 流函数$f(x,y)$的三个性质:容量限制①,斜对称②,流量守恒③ ##### 最大流 $max(\Sigma_{(S,v)\in E}f(S,v))$;(前提是这个流函数$f$合法;所以源点流出多少汇点就有多少) ##### 残量网络 任意时刻,图中所有节点和剩余容量大于$0$的边构成的集合; ##### 最小割 去掉后使得源汇点不再联通的边集合,且集合内所有边容量之和最小;可以证明,最小割"等于"最大流; 可以求出最大流以后再遍历一次残量网络,标记此时可以到达的所有点,所有连接可以到达的点和不可以到达的点的边就是图的最小割; ##### 必须边和可行边 当最大匹配为一组完备匹配的时候,$(u,v)$边为一条必须边当且仅当$(u,v)$是匹配边,$x,y$两点在$G_1$中属于不同的强联通分量;$(u,v)$边为一条可行边当且仅当$(u,v)$是匹配边,$x,y$两点在$G_1$中属于相同的强联通分量; ##### 费用流 每一条边除了有一个容量$c(i,j)$,还有单位容量的费用$w(i,j)$;所以该条边满流的时候花费为$c(i,j)*w(i,j)$;然后即可求最小费用最大流,最大费用最大流; ##### 相关算法 ##### $Edmonds-Karp$增广路算法 记每条边的剩余容量$w(u,v)$,如果能从源点出发找到一条路径上剩余容量都大于$0$的边的集合,那么总流就可以增加$min({}_{(u,v)\in E'}f(u,v))$的流量;复杂度为$O(nm^2)$,可以处理$10^3$~$10^4$的数据; ```cpp void add(int f,int t,int w) { edge[++cnt].to=t; edge[cnt].w=w; edge[cnt].nxt=head[f]; head[f]=cnt; }//利用i和i^1互为反边的性质,要么tot=0,tot++;要么tot=1,++tot int bfs() { memset(v,0,sizeof v); queue<int>q; q.push(s);vis[s]=1; incf[s]=INF;//到现在这个点为止的剩余容量的最小值 while(q.size()) { int now=q.front();q.pop(); for(int i=head[now];i;i=edge[i].nxt) if(edge[i].w) { int to=edge[i].to; if(vis[to])continue; incf[to]=min(incf[to],edge[i].w); pre[to]=i;//记录to前驱i以寻找路径 q.push(to);vis[to]=1; if(to==t)return 1; } } return 0; } void update() { int now=t; while(now!=s) { int i=pre[now]; edge[i].w-=incf[t]; edge[i^1].w+=incf[t]; now=edge[i^1].to;//edge[i^1].to==edge[i].from } maxflow+=incf[t]; } int main() { s=1;t=n;//源汇点 cnt=1; for(int i=1;i<=m;i++) scanf("%d%d%d",&u,&v,&w), add(u,v,w), add(v,u,0); while(bfs())update(); printf("%d",maxflow); } ``` ##### $Dinic$算法 $EK$算法每次遍历整个残量网络,却只找一条增广路;$dinic$通过构造一个分层图使得每个点带上$bfs$序($bfs$ 序!),保证按照$bfs$序的大小升序流,就可以保证效率;每次寻找增广路的时候重新构造分层图,然后$dfs$寻找增广路;可以有两种优化,当前弧优化和整体流量优化;复杂度$O(n^2m)$,特别的,在处理二分图最大匹配的时候,为$O(m\sqrt{n})$;可以处理$10^4$~$10^5$的数据;当前弧优化的目的:记录 $dinic$ 算法在每一个节点尝试继续增广时增广到了哪一条边。 ```cpp void add(int f,int t,int w) { edge[++cnt].to=t; edge[cnt].w=w; edge[cnt].nxt=head[f]; head[f]=cnt; } int bfs() { memset(dep,0,sizeof dep); queue<int>q; q.push(s);dep[s]=1; now[s]=head[s];//now数组用来使用当前弧优化 while(q.size()) { int x=q.front();q.pop(); for(int i=head[x];i;i=edge[i].nxt) { int to=edge[i].to; if(edge[i].w&&dep[to]==0) { q.push(to); now[to]=head[to]; dep[to]=dep[x]+1; if(to==t)return 1; } } } return 0; } int dinic(int x,int flow) { if(x==t)return flow; int rest=flow,i;//rest for what? for(i=now[x];i!=0&&rest!=0;i=edge[i].nxt)//当前弧优化 { int to=edge[i].to; if(edge[i].w&&dep[to]==dep[x]+1) { int k=dinic(to,min(rest,edge[i].w));//?? if(k==0)dep[to]=0;//直接剪枝 edge[i].w-=k; edge[i^1]+=k; rest-=k;//其实这里可以直接返回,不过用(整体流量优化)即多路增广的方法 //return k; } } now[x]=i;//当前弧优化,避免重复遍历从x出发不可扩展的边 return flow-rest;//flow-(flow-Σk)=Σk } int main() { s=1,t=n; cnt=1; for(int i=1;i<=m;i++) scanf("%d%d%d",&u,&v,&w), add(u,v,w), add(v,u,0); int flow=0; while(bfs()) while(flow=dinic(s,INF))maxflow+=flow; printf("%d",maxflow); } ``` ##### $Edmonds-Karp$增广路算法$+SPFA$ 注意到$EK$算法中使用$bfs$来寻找任意一条增广路,只要把$bfs$改成$SPFA$来寻找一条费用最小/最大的增广路即可; ```cpp ``` ##### $Dinic$费用流 没有仔细看,应该也是一个不同的优于$EK$费用流的算法; ##### $zkw$费用流 $unDone$ ##### 预流推进 $unDone$ ```cpp ``` $ISAP$ 前文的$dinic$和$EK$算法都是$SAP$算法,而这是$Improved$的算法;优点在于码量小,对于着重考察建模的网络流,求最大流的过程就变成了“黑盒”算法,这是一个极其节省时间的方法; ``` cpp ``` ##### 相关技巧 ##### 点边转化 有向图中,使得点特征转化成边特征的方法:若有边$(p,x)(q,x)(x,r)$,将所有点$x$拆成$x,x'$,连边$(p’,x)(q’,x)(x,x')(x',r)$,其中,任一点$x$称为入点,$x'$称为出点; 无向图中,使得点特征转化成边特征的方法:若有边$(p,x)(q,x)$,将$x$拆成$x,x'$,连边$(p',x)(q',x)(x,x')(x',p)(x',q)$,其中,$x$称为入点,$x'$称为出点; 即,点转边为拆点,入边连入点,出边连出点,出入点相连;转化之后都是有向边; 边特征转化成点特征的方法:边$(x,y)$之间加一个点$e$,使得边变成$(x,e)(e,y)$; 例题:$POJ1966$ ##### 本质特征 最大流的正确性依赖于它的每一条 $s-t$ 流都与一种实际方案对应; 这是网络流优化暴力的本质特征; 这条特征可以从例题:$POJ3281$ 中得到充分体现; 最小割是最大流的对偶问题; ##### 实际意义 作为一种优化暴力的算法,注意到在网络流图中,每一条可行流对应着原题中的一种**相对完整**的操作方式,即可以通过加法原理,构成原题的一种可行方案。对于建模,对于建模的过程,可以对具体的**状态**重建点,比如餐巾分配问题里对于每一个月建立点。源点可以作为每个方案的开始,汇点作为结束。还可以是本身题中给定了的图的拓展,如刘振西题中的每一个点的入点表示的是出发位置,出点表示的是结束位置,而那里为了使得牛群可以不移动,又连了一条$(i,i',flow:inf,cost:0)$的边。这里提及的就是对于单独的点不够表示操作时,可以考虑拆点,使得入点与出点之间的边表示对点的操作。比如可以连一条$flow:1$的边表示只可以走一次此点,从最小割的角度考虑,又可以表示将这个点删除,使得图不连通。 而对于边及边上的容量,边上的费用来说,通常一般的最大流题目,流量通常就是题目中所给的路径上可以通过的人数、车数等等。加上费用就是花费的体力、金钱什么的。有一类费用流题目,要求最小花费,这时流量就变成了无用或者限制操作次数的东西,将花费放到每一条边的费用之上来跑最小/大费用最大流。如典型的$k$可重路径最大值问题;又例如石头剪刀布在每个点到汇点上连接的差分费用边。那里面就是利用每一条未定向的有向边统计入度来差分。以及,对于边上的通过$'1'$流量来确定的删除操作,只能对应$0/1$的操作,而对于$1/2$的情况,不能直接处理,但是可以先全体差分转换成$0/1$处理,例如$JOJ2453$。 ##### 发散思维 $WOJ1124$ 题中胜者2分,败者0分,平者1分;可以认为是两队分配2分;于是此时可以建图:对于比赛**状态**建立点$i$,连边$(s,i,2)$,$(i,team_{i_1},2)$,$(i,team_{i_2},2)$,每个球队连边$(j,t,flow:Score[n]-Score[j])$表示最多可以获得的分数。当题中不允许平局出现的时候,连边会发生以下变化: $(s,i,2)->(s,i,1)$; $(j,t,Score[n]-Score[j])-> (j,t,floor((score[n]-score[j])/2));$ $(i,u,2), (i,v,2) -> (i,u,1), (i,v,1)$; 这就是所谓的化$'2'$为$'1'$;只不过是另一种写法即$1/2$倍数;将需求减半,利益减半,使得分配分数改成确定状态(输赢),这也可以转换。 $SPOJ962$ 按顺序往$A$点,$B$点,$C$点,看似不可解,其实只需要把$B$点做源点,同时到往$AC$就可以了。这里体现的是将网络流图中的三层元素中的中间元素作为突破,把三种元素中具有单独意义,特殊性质的放到网络流图的中间这一层来。可以参考小行星和$POJ3281$。 $ZOJ2532$ 如何判定网络的最小割是否唯一?同样求一次最大流后在残量网络$R$中以正向弧对$s$、以正向弧的逆对$ t $作 $DFS$,只不过这次只用一个$flag[i]$标记点$i$是否在两次$DFS$中被探访过。最后扫一遍所有点,如果存在没有被探访过的,则说明最小割不唯一。这时候表示的是有多条在单一(即一条链状的)同一路径上的流量相同的边。(还有其他情况吗?) $Ural1277$ 此题是**无向图点带权的点连通度问题**。将每个点$i$拆成两个点$i’$,$i’’$,除匪窝$s$和美术馆$t$外加边$(i’, i’’, Ri)$,将每条无向边$(i, j)$分解为$(i’’, j’)$,$(j’’, i’)$。令$s’’$为源,$t’$为汇,求一次最小割即为结果。如果警察只能驻守在边上,该如何做?直接以原图为网络求最小割即可。如何求**无向图的边连通度**?任选一点固定为源,枚举汇点求最小割,取最小值即为所求。如何求**无向图的点连通度**? 令本题中的 Ri = 1,以度最小的顶点为源,枚举汇点求最小割,取一个最小值即为所求。 $POJ2396$ 经典的**有源汇有上下界网络的可行流问题**。 第一步:建立无源汇有上下界的网络模型。每行$ i $作为一个点并连边$(s, i, Ri, Ri)$,每列$ j $作为一个点并连边$(j, t, Cj, Cj)$,设$ Uij, Lij$分别表示第$ i $行第$ j $列元素的上下界,初始时设$ Uij=∞, Lij=0$。按照给定的约束条件不断调整$ Uij, Lij$,若出现$ Lij > Uij $的情况则已经不存在合法解。对所有元素加 边$(i, j, Lij, Uij)$。另添加边$(t, s, 0, ∞)$消去原网络的源汇。 第二步:转化为最大流模型。新建源 $s’$和汇$ t’$,对每条下界大于$ 0 $的边$(i, j, Lij, Uij)$,加边$(i, t’, 0, Lij), (s’, j, 0, Lij)$。 若新网络中最大流等于所有下界之和,则原网络存在可行流,即存在满足所有约束条件的矩阵。怎样求无源汇有上下界网络的可行流?由于有源汇的网络我们先要转化成无源汇,所以本来就无源汇的网络不用再作特殊处理,其他操作同第二步。怎样求无源汇有上下界网络的最大流、最小流?一种简易的方法是采用二分的思想,不断通过可行流的存在与否对$(t, s)$边的上下界$ U, L $进行调整。求最大流时令$ U = ∞$并二分$ L$;求最小流时令$ L=0 $并二分$ U$。道理很简单,因为可行流的取值范围是一段连续的区间,我们只要通过二分找到有解和无解的分界线即可。见下图: ![](C:\Users\lenovo\Desktop\QQ截图20200324000713.png) $HOJ2543$ 如果费用函数不是分段线性的,该如何做?枚举流量为$ 0, 1, 2, …$,计算对应的费用$ cost[0], cost[1], cost[2],$$ …$,然后依次加入容量为$ 1$,费用为$ cost[0], cost[1]-cost[0], $$cost[2]-cost[1]-cost[0], …$的边。最小费用流以及费用函数的凸性保证了流一定会先充满费用较小的边,再流向费用较大的边,而这些费用的和正好等于流量为$ x$时的总费用,是正确的流。具体可以参考《剪刀石头布》这道题。 ------ ##### 写在最后 会继续更新的知识体系;$unDone$表示的有可能是没学也有可能是没有总结;(实在太难写了)
标签:匹配,int,短路,edge,#####,now,233 来源: https://www.cnblogs.com/yzyl-Leo-wey/p/16387598.html
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。