图论专题

最短路

算法

常见算法:SPFA,Dijkstra,Floyd,过于基础,不予列举

例题1

(因为没有原题,这里只有我的实现代码,不保证全对……)这里默认没有重边,自环,边权为正,有向图。

找出一条最短路径(1.1)

正常跑最短路,在跑的过程中记录一个pre,最后依次输出即可。

类似题目(这题无向图,起点 \(1\),终点 \(n\)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<vector>
#define gc()(p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
#define ll long long
#define pll pair<ll,ll>
using namespace std;
const int MN=5e5+5;
ll n,m,s,t,head[MN],tot,pre[MN],dis[MN];
bool vis[MN];
//n为点数,m为边数,s为起点,t为终点,pre为上一个点,dis为到当前点的最短路长度
struct edge{ll to,nxt,w;}e[MN];
priority_queue<pll,vector<pll>,greater<pll> > q;
char buf[1<<23],*p1=buf,*p2=buf;
void write(ll n){if(n<0){putchar('-');write(-n);return;}if(n>9)write(n/10);putchar(n%10+'0');}
ll read(){ll x=0,f=1;char ch=gc();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=gc();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=gc();}return x*f;}
void add(ll u, ll v, ll w){e[++tot].nxt=head[u];head[u]=tot;e[tot].to=v;e[tot].w=w;}
void print(ll u){
    if(u==s){write(u),putchar(' ');return;}
    print(pre[u]);write(u);putchar(' ');
}
int main(){
    n=read();m=read();s=read();t=read();
    for(int i=1; i<=m; i++){
        ll u=read(),v=read(),w=read();
        add(u,v,w);
    }
    for(int i=1; i<=n; i++) dis[i]=1e18;
    dis[s]=0;q.push({0,s});
    while(!q.empty()){
        ll u=q.top().second;q.pop();
        if(vis[u]) continue;
        vis[u]=true;
        for(int i=head[u]; i; i=e[i].nxt){
            ll v=e[i].to,w=e[i].w;
            if(dis[v]>dis[u]+w){
                pre[v]=u;dis[v]=dis[u]+w;
                q.push({dis[v],v});
            }
        }
    }
    print(t);putchar('\n');
    return 0;
}

计算最短路径上的边数最少是多少(1.2)

跟上一个类似,在最短路的同时加上第二关键字为路径长度。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<vector>
#define gc()(p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
#define ll long long
#define pll pair<ll,ll>
using namespace std;
const int MN=5e5+5;
ll n,m,s,t,head[MN],tot,num[MN],dis[MN];
bool vis[MN];
//n为点数,m为边数,s为起点,t为终点,num为到当前点的最短路的点数,dis为到当前点的最短路长度
struct edge{ll to,nxt,w;}e[MN];
priority_queue<pll,vector<pll>,greater<pll> > q;
char buf[1<<23],*p1=buf,*p2=buf;
void write(ll n){if(n<0){putchar('-');write(-n);return;}if(n>9)write(n/10);putchar(n%10+'0');}
ll read(){ll x=0,f=1;char ch=gc();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=gc();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=gc();}return x*f;}
void add(ll u, ll v, ll w){e[++tot].nxt=head[u];head[u]=tot;e[tot].to=v;e[tot].w=w;}
int main(){
    n=read();m=read();s=read();t=read();
    for(int i=1; i<=m; i++){
        ll u=read(),v=read(),w=read();
        add(u,v,w);
    }
    for(int i=1; i<=n; i++) dis[i]=num[i]=1e18;
    dis[s]=num[s]=0;q.push({0,s});
    while(!q.empty()){
        ll u=q.top().second;q.pop();
        if(vis[u]) continue;
        vis[u]=true;
        for(int i=head[u]; i; i=e[i].nxt){
            ll v=e[i].to,w=e[i].w;
            if(dis[v]>dis[u]+w){
                dis[v]=dis[u]+w;num[v]=num[u]+1;
                q.push({dis[v],v});
            }
            if(dis[v]==dis[u]+w&&num[v]>num[u]+1) num[v]=num[u]+1;
        }
    }
    write(num[t]);putchar('\n');
    return 0;
}

对于每条边,判断其是否可能在最短路上(1.3)

保留所有可能在最短路上的边,得到一个 DAG,所有最短路径都在该图上,该图所有 \(s\) 到 \(t\) 的路径对应原图的一个最短路,我们称之为最短路图。

代码鸽中……

找出字典序(经过的点编号)最小的最短路径

在最短路图上贪心,不断往编号最小的点走。

代码鸽中……

计算最短路径的条数

原题传送门

在最短路图上 dp。

代码鸽中……

二分图

二分图的定义

二分图指节点由两个集合组成,且两个集合内部没有边的图。换言之,存在一种方案,将节点划分成满足以上性质的两个集合。

二分图的性质

  1. 如果两个集合中的点分别染成黑色和白色,可以发现二分图中的每一条边都一定是连接一个黑色点和一个白色点。
  2. 二分图不存在长度为奇数的环。

二分图的判定

从任意一个点开始 DFS 染色,如果相邻的点未染色则染成相反的颜色,如果相邻的点出现同色则不是二分图。

如果图不连通,需要对每个连通块分别判断。

图的匹配

在图论中,假设图 \(G=(V,E)\),其中 \(V\) 是点集,\(E\) 是边集。

一组两两没有公共点的边集 \(M(M\in E)\) 称为这张图的匹配

定义匹配的大小为其中边的数量 \(|M|\) ,其中边数最大的 \(M\)最大匹配

当图中的边带权的时候,边权和最大的为最大权匹配

匹配中的边称为匹配边,反之称为未匹配边

一个点如果属于 \(M\) 且为至多一条边的端点,称为匹配点,反之称为未匹配点

增广路

增广路的定义

交错路:由匹配边与非匹配边交错而成的路径。
增广路:是始于非匹配点且终于非匹配点(除了起始的点)的交错路。增广路中边的数量是奇数。

途中经过的边必须为“非匹配边 \(\rightarrow\) 匹配边 \(\rightarrow\) 非匹配边 \(\rightarrow\) 匹配边 \(\rightarrow\cdots\rightarrow\) 非匹配边”这样的交替式,不能重复经过一个节点多次,增广路上非匹配边比匹配边数量多 \(1\),如果将增广路上的匹
配边和未匹配边反转,则匹配数量会增加 \(1\) 且依然是交错路。

增广路定理(Berge's lemma)

在一个图中,一个匹配是最大匹配当且仅当不存在增广路。

通过反证法来证明。

假设 \(M\) 是一个匹配且存在增广路 \(P\),那么可以通过将增广路 \(P\) 上的匹配边和非匹配边反转,得到一个新的匹配 \(M'\),且 \(M'\) 的匹配数比 \(M\)\(1\)

因此,\(M\) 不是最大匹配。反之,如果 \(M\) 不是最大匹配,那么存在一个比 \(M\) 更大的匹配 \(M'\),这意味着存在增广路 \(P\)。这样,我们就证明了 \(M\) 是最大匹配当且仅当不存在增广路 。

由增广路定理可知我们求最大匹配的核心思路:枚举所有未匹配点,找增广路径,直到找不到增广路径。

事实上,对于每个点只要枚举一次就好,证明如下:

假设某一轮沿着增广路 \(a\rightarrow b\) 增广后,新增了以未匹配点 \(x\)
为起点的增广路 \(P_x\) ,则 \(P_x\) 必与 \(a\rightarrow b\) 有公共边(否则 \(P_x\) 不可能是因此次增广而新增的)。在 \(P_x\)\(a\rightarrow b\) 取得公共边时,由于\(a\rightarrow b\) 是交错路,意味着相交点在 \(a\rightarrow b\) 内的两邻边是不同类型的;

因而增广前 \(x\) 就能走到 \(a\rightarrow b\) 中的某个未匹配点,说明此前已存在从 \(x\) 出发的增广路,即已枚举过的未匹配点不再可能作为增广路起点。

posted @ 2025-02-13 23:55  naroto2022  阅读(24)  评论(0)    收藏  举报