图论笔记

最短路

Floyd

for(int k=1;k<=n;k++)
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            f[i][j]=min(f[i][j],f[i][k]+f[k][j]);

P13519

在一个边权均为0的无向图中每次将一条边边权变为1
求所有点到其他点最短路之和

1.可离线处理时加边减边操作可互换。因为最短路松弛后结果不增,可以考虑将减边转化为加边。
2.只改变一条边边权时只用重新用这条边松弛一遍,不用\(N^3\)写法

for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++)
        f[i][j]=min(f[i][j],min(f[i][x]+f[y][j],f[i][y]+f[x][j]));

dijstra

结构体写法

struct nod
{
    int x,w;
    bool operator <(nod other)const//默认大根堆是用'<'实现的 所以重载的是'<'
    {
        return w>other.w;//将'<'重载为'>'
    }
};

主体

q.push({st,0});
dis[st]=0;
while(!q.empty())
{
    nod top=q.top();
    q.pop();
    if(vis[top.x])  continue;
    vis[top.x]=1;
    for(auto to:g[top.x])
    {
        if(dis[to.x]>to.x+top.x)
            dis[to.x]=to.x+top.x,q.push({to.x,dis[to.x]});
    }
}

对比SPFA

while (!q.empty()) {
    int u = q.front();
    q.pop(), vis[u] = 0;
    for (auto ed : e[u]) {
      int v = ed.v, w = ed.w;
      if (dis[v] > dis[u] + w) {
        dis[v] = dis[u] + w;
        cnt[v] = cnt[u] + 1;  // 记录最短路经过的边数
        if (cnt[v] >= n) return false;
        // 在不经过负环的情况下,最短路至多经过 n - 1 条边
        // 因此如果经过了多于 n 条边,一定说明经过了负环
        if (!vis[v]) q.push(v), vis[v] = 1;
      }
    }
  }

只有上一次被松弛的结点,所连接的边,才有可能引起下一次的松弛操作。因此队列中所存元素是所有待松弛的点。
dij中每个点最多被松弛一次,SPFA中vis数组记录的则是队列中元素。

P1462

有一个带权无向图,求满足路径长度\(dis\leq b\)时路径上点权最大值的最小值。

最大值最小/最小值最大问题 考虑二分
对点权二分答案跑dij。

Tarjan

void Scc(int x)
{
    s.push(x);
    is[x]=1;
    dfn[x]=low[x]=++tot;
    for(auto to:g[x])
    {
        if(!dfn[x])  Scc(to),low[x]=min(low[x],low[to]);
        if(is[x])  low[x]=min(low[x],dfn[to]);
    }
    if(low[x]==dfn[x])
    {
        tot_scc++;
        while(1)
        {
            int top=s.top();
            s.pop();
            is[top]=0;
            if(top==x)  break;
        }
    }
}

树剖

一条重链的起始是重儿子的父节点。
初始化两次dfs。

void dfs1(int x,int father)
{
    fa[x]=father,dep[x]=dep[father]+1,sz[x]=1;
    for(auto to:g[x])
    {
        if(to!=father)  dfs1(to,x);
        sz[x]+=sz[to];
        if(sz[to]>sz[son[x]])  son[x]=to;
    }
}
void dfs2(int x,int ftop)
{
    top[x]=ftop,dfn[x]=++tot,id[tot]=x;
    if(son[x])  dfs2(son[x],ftop);
    for(auto to:g[x])
        if(to!=son[x]&&to!=fa[x])  dfs2(to,to);
}

重链剖分求LCA

int LCA(int x,int y)
{
    while(top[x]!=top[y])
    {
        if(dep[top[x]]<dep[top[y]])  swap(x,y);
        x=fa[top[x]];//注意这里跳到链头是ftop
    }
    if(dep[x]<dep[y])  return x;
    else  return y;
}

最小生成树

Kruskal贪心加边。

复习一下并查集。
路径压缩

//查找
int find(int x)
{
    if(a[x]==x)  return x;
    else  return a[x]=find(a[x]);
}

//合并
void Merge(int x,int y)
{
    a[find(x)]=a[find(y)];
}

启发式合并:将节点较少的树连到另一棵。

//初始化
void Init()
{
    for(int i=1;i<=n;i++)  sz[i]=1,pa[i]=i;
}
//查找
int find(int x)
{
    if(pa[x]==x) return x;
    else  return a[x];
}
//合并
void merge(int xx,int yy)
{
    int x=find(xx),y=find(yy);
    if(x==y)  return ;
    if(sz[x]<sz[y])  swap(x,y);
    pa[y]=x;
    sz[x]+=sz[y];
}
posted @ 2026-07-22 21:10  zzzdc  阅读(3)  评论(0)    收藏  举报