【学习笔记】图论杂谈(三)
懒得整了,有时间再来整
1.图论经典问题
最小点覆盖:覆盖边
最小支配集:覆盖点
最大独立集:
2.最短路
二进制分组最短路
差分约束:最长路求最小值,最短路求最大值
3.连通性
tarjan的dfn序是逆拓扑序,枚举即可
边的判重方式:哈希表记录每个点的前驱节点,如果是u则跳过,否则更新u
有的题目需要记录割点割边,即它们在后续起到作用 P3225
4.有向图博弈
P9169 [省选联考 2023] 过河卒
建反边,从末态开始算01SG函数
5.生成树
瓶颈生成树
最大生成树
瓶颈最短路:kruskal生成树
6.状态图
状态图P5100
7.查分约束
最短路:考虑 \(x_0=0\) ,解存在, \(0\) 出发能达到 \(v\),\(dis_0=0\),那么 \(dis_v\) 就是 \(x_v\) 的最大值。
最长路:反之
8.TWO-SAT
N-SAT NPC问题
蕴含图(满足原图时同时满足的图)
9.优化建图
前后缀优化建图CF587D:一个点代表一个前后缀
线段树优化建图
10.Tarjan优化LCA
(u,v)
如果祖宗关系:find(v)=v
非祖宗关系:find(v)=lcd(u,v)(未合并)
11.BEST定理:计算有向欧拉回路数量
引理 1 (最后离开树)
给定一条欧拉回路。对每个 𝑣 ≠ 𝑠,取最
后一次离开 𝑣 的边。它们构成一棵以 𝑠 为根的内向树。
定理 2 (BEST 定理)
从固定边开始的欧拉回路数量为 \(r_s ∏_{v∈V}(outdeg(v) − 1)!\)
矩阵树定理求 \(r_s\)
12.最小树形图
首先给出树形图的定义:(可以近似理解为有向图上的生成树)
- 有向图中定义
- 无环
- 根节点可以到达任意一个节点
- 根节点入度为 0 ,其他节点入度为 1
然后是最小树形图:
边权和最小的树形图。
12.1朱刘算法
每个点选最小入边
之后如果有环,缩点,更改入环边权值
反复操作,直到无环
O(VE)
不定根
\(W=\sum w\)
建虚拟源点,对各个点连 \(W+1\) 边权的边
\(ans>=2W\) 则无解(两个根),否则减去 \(W+1\) 得到原值
13.匹配
概念
1
设一般无向图 𝐺 = (𝑉 , 𝐸)。匹配 𝑀 ⊆ 𝐸 中任意两条边没有公共端点。
交错路:匹配边和非匹配边交错
增广路(匹配图):两端不匹配的交错路
增广路(Dinic):在“当前残量网络”中,从源点 S 到汇点 T 的一条路径,并且路径上的每一条边都必须严格满足“剩余容量 > 0” 且 “层级(Level)逐层 +1
两个定义本质相同,匹配里的增广路,是“去掉 S 和 T 后,将容量全设为 1 的残量网络增广路”。
沿增广路把“匹配/非匹配”取反,内部点仍恰好关联一条匹配边,两端由未匹配变为已匹配,因此 |𝑀| 增加 1。
2
一条关于 M 的增广路 P,必须同时满足以下 3 个条件:
条件1:这条路的起点和终点,在当前的匹配 M 中,都必须是尚未匹配的顶点
条件2:这条路上的边,必须是“未匹配边 -> 匹配边 -> 未匹配边 -> 匹配边……”这样交替出现的。
条件3:因为路径两端的边都是“未匹配边”,所以这条路上未匹配边的数量,永远比匹配边多 1 条,整条路径的边数一定是奇数。
引理
匹配的对称差:两个匹配的对称差等于他们的边集的异或
-
其每个连通分量都是 𝑀, 𝑀′ 边交替的路或偶环。
-
环与首尾分属 𝑀, 𝑀′ 的交错路中,两类边数相同;
-
首尾均为 𝑀′ 边时 𝑀′ 边多一条,首尾均为 𝑀 边时反之。
Berge 引理:匹配 𝑀 最大,当且仅当不存在关于 𝑀 的增广路。
定理
柯尼希定理:二分图的最大匹配大小等于最小点覆盖大小。
算法
匈牙利(kuhn)算法
求解二分图最大匹配问题
暴力找增广路,翻转
优势在于写法简单 \(O(EV)\)
#include<cstring>
#include<iostream>
using namespace std;int n1,n2,m;
const int N=510,M=5e4+10;
int head[N],to[M],ne[M],vis[N],match[N],idx;
void add(int u,int v){
to[++idx]=v;ne[idx]=head[u];head[u]=idx;
}
bool find(int u){
for(int i=head[u];i;i=ne[i]){
int j=to[i];
if(!vis[j]){
vis[j]=1;
if(!match[j]||find(match[j])){
match[j]=u;
return 1;
}
}
}
return 0;
}
int main(){
cin>>n1>>n2>>m;
while(m--){
int u,v;cin>>u>>v;
add(u,v);
}
int res=0;
for(int i=1;i<=n1;i++){
memset(vis,0,sizeof vis);
if(find(i)) res++;
}
cout<<res;
}
HK(Hopcroft-Karp)算法
分层图加速匈牙利
比Dinic常数小一点,都是 \(O(E\sqrt V\)),但是其写法和Dinic惊人相似,本质是一个算法
KM算法
求解二分图最大权完美匹配,不建议用网络流替代,会很慢
KM 算法:复杂度是 O(n³)。
替代品(最小费用最大流):在稠密图(带权匹配通常是完全二分图)中,复杂度约为 O(n³ log n) 甚至 O(n⁴)(取决于具体实现,如 SSP 增广路算法)。
//dfs版本,复杂的O(n^4)
#include<bits/stdc++.h>
#define int long long
#define inf 2e9
using namespace std;int n;
const int N=510;
int mp[N][N];
int lx[N],ly[N],vx[N],vy[N],march[N];
int d;
bool dfs(int u){
vx[u]=1;
for(int i=1;i<=n;i++){
if(vy[i]) continue;
if(lx[u]+ly[i]==mp[u][i]){
vy[i]=1;
if(!march[i]||dfs(march[i])){
march[i]=u;
return 1;
}
}
else d=min(d,lx[u]+ly[i]-mp[u][i]);
}
return 0;
}
void KM(){
for(int i=1;i<=n;i++){
while(1){
memset(vx,0,sizeof vx);
memset(vy,0,sizeof vy);
d=inf;
if(dfs(i)) break;
for(int j=1;j<=n;j++){
if(vx[j]) lx[j]-=d;
if(vy[j]) ly[j]+=d;
}
}
}
}
signed main(){
memset(mp,-0x3f,sizeof mp);
memset(lx,-0x3f,sizeof lx);
int m;cin>>n>>m;
while(m--){
int a,b,c;cin>>a>>b>>c;
mp[a][b]=max(mp[a][b],c);
lx[a]=max(lx[a],c);
}
KM();
int ans=0;
for(int i=1;i<=n;i++){
ans+=mp[march[i]][i];
}
cout<<ans<<"\n";
for(int i=1;i<=n;i++) cout<<march[i]<<" ";
return 0;
}
//BFS版
#include<bits/stdc++.h>
#define int long long
#define inf 2e9
using namespace std;int n;
const int N=510;
int mp[N][N];
int lx[N],ly[N],vx[N],vy[N],march[N],lm[N];
int slack[N];
int pre[N];
void aug(int v){
int t;
while(v){
t=lm[pre[v]];
lm[pre[v]]=v;
march[v]=pre[v];
v=t;//跳了两步,保证右部点
}
}
void bfs(int s){
memset(vx,0,sizeof vx);
memset(vy,0,sizeof vy);
memset(slack,0x3f,sizeof slack);
queue<int> q;
q.push(s);
while(1){
while(q.size()){
int u=q.front();q.pop();
vx[u]=1;
for(int v=1;v<=n;v++){
if(vy[v]) continue;//被询问过直接跳过
if(lx[u]+ly[v]-mp[u][v]<slack[v]){//保证slack尽量小
slack[v]=lx[u]+ly[v]-mp[u][v];
pre[v]=u;//记录当前链
if(slack[v]==0){
vy[v]=1;
if(!march[v]){
aug(v);//翻转链
return ;
}
else q.push(march[v]);//拓展相等子图
}
}
}
}
int d=inf;
for(int i=1;i<=n;i++){
if(!vy[i]) d=min(d,slack[i]);//对非相等子图求最小边
}
for(int i=1;i<=n;i++){
if(vx[i]) lx[i]-=d;
if(vy[i]) ly[i]+=d;
else slack[i]-=d;//从定义出发
}
//连带修改
for(int i=1;i<=n;i++){
if(vy[i]) continue;
if(slack[i]==0){
vy[i]=1;
if(!march[i]){
aug(i);
return ;
}
else q.push(march[i]);
}
}
}
}
void KM(){
for(int i=1;i<=n;i++){
bfs(i);
}
}
signed main(){
memset(mp,-0x3f,sizeof mp);
memset(lx,-0x3f,sizeof lx);
int m;cin>>n>>m;
while(m--){
int a,b,c;cin>>a>>b>>c;
mp[a][b]=max(mp[a][b],c);
lx[a]=max(lx[a],c);
}
KM();
int ans=0;
for(int i=1;i<=n;i++){
ans+=mp[march[i]][i];
}
cout<<ans<<"\n";
for(int i=1;i<=n;i++) cout<<march[i]<<" ";
return 0;
}
//BFS的优化在于保留标记,修改边权之后接力迭代
13.偏序集最长反链(两两不可比较)
有限集合 𝑃 上的关系 ≼ 若满足自反、反对称、传递,则称 (𝑃, ≼)
为偏序集;记 𝑢 ≺ 𝑣 表示 𝑢 ≼ 𝑣 且 𝑢 ≠ 𝑣。
链中的元素两两可比较,反链中的元素两两不可比较。链划分要求
每个元素恰属于一条链。
Dilworth定理:有限偏序集的最长反链大小等于最小链划分大小。
14.Hall定理
Hall定理:二分图 𝐺 = (𝑋, 𝑌 , 𝐸) 存在 𝑋 的完美匹配,当且仅当任意 𝑆 ⊆ 𝑋 都满足|𝑁(𝑆)| ≥ |𝑆|.
缺陷Hall定理:义缺陷 def(𝑆) = |𝑆| − |𝑁(𝑆)|.最大匹配覆盖的左部点数满足 \(|𝑀∗| = |𝑋| − max_{𝑆⊆𝑋}def(𝑆).\)
P14598:如果左部点若干集合独立,可以分别求解再相加
带权hall定理:
EX
射线法(计算几何)

浙公网安备 33010602011771号