图论——板子

图论

链式前向星

自己呢,总是忘了,head为0,cnt=0,直接用

const int N=1e5+5;
const int M=3e6+5;
struct node
{
    int v,next,w;
    bool operator<(const node& b)const
    {
        return w<b.w;
    }
}e[M];
int cnt=0,head[N],vis[N],dis[N];
void add(int from,int to,double w)
{
    e[++cnt].next=head[from];
    e[cnt].v=to;
    e[cnt].w=w;
    head[from]=cnt;
}

最小生成树

克鲁斯卡尔算法(Kruskal算法)——时间复杂度O(eloge)
特性:稀疏图和记录路径
普里姆算法——时间复杂度O(n^2)
最小生树是否唯一问题:(次小生成树问题)
朴素算法(m^2)
1:暴力删除最小生树上的
2:判断权值和以及是否是一棵树
优化后,理论(mlogm)跑一次克鲁斯卡尔即可
边权相等的边,边两点不同的并查集的边数<=所在不同的并查集个数-1
保证最小生成树唯一

最小生成树:所有边权之和最小

瓶颈生成树:定义无向图G,G的瓶颈生成树是一棵 “ 树上最大边权值 edge 在G的所有生成树中最小 ” 的生成树,
这样的生成树可能不止一棵。瓶颈生成树的值为树上最大边权值 edge

结论:
最小生成树一定是瓶颈生成树
瓶颈生成树不一定时最小生成树

暂时理解:最小瓶颈生成树——瓶颈生成树一个意思

最短路

Dijkstra:适用于权值为非负的图的单源最短路径,朴素算法O(n2)用斐波那契堆的复杂度O(E+VlgV)
解决单源最短路径问题常用Djkstra算法,用于计算一个顶点到其他所有 顶点的最短路径。Djkstra 算法的主要特点是以起点为中心,逐层向外扩展,每次都会取一个最近点继续扩展,直到取完所有点为止。

BellmanFord:适用于权值有负值的图的单源最短路径,并且能够检测负圈,复杂度O(VE)

SPFA:适用于权值有负值,且没有负圈的图的单源最短路径,论文中的复杂度O(kE),k为每个节点进入Queue的次数,且k一般<=2,但此处的复杂度证明是有问题的,其实SPFA的最坏情况应该是O(VE).

在这里插入图片描述
先给出结论:
(1)当权值为非负时,用Dijkstra。
(2)当权值有负值,且没有负圈,则用SPFA,SPFA能检测负圈,但是不能输出负圈。
(3)当权值有负值,而且可能存在负圈,则用BellmanFord,能够检测并输出负圈。
(4)SPFA检测负环:当存在一个点入队大于等于V次,则有负环,后面有证明。
严格最短路是否唯一问题:(次严格最短路问题)
两次dijkstra,然后枚举


dis1[u]+dis+dis2[v]=dis1[n]
dis1[v]+dis+dis2[u]=dis1[n]

满足则标记为严格最短路必经路径

floyd暴力求最短路–O(n3)

const int INF=0x3f3f3f3f;
const int N=500;
int G[N][N];
void floyd(int n)//图的最短路路径,枚举一遍
{
    for(int k=0;k<n;k++)
        for(int i=0;i<n;i++)
            for(int j=0;j<n;j++)
                G[i][j]=min(G[i][j],G[i][k]+G[k][j]);
}

Disjkstra算法

O(n2

int dis[N],vis[N],G[N][N],t,n;
void dijkstra(int u)
{
    memset(dis,inf);
    dis[u]=0;
    int v=u,minn;
    vis[v]=true;
    for(int i=1; i<=n; ++i)
    {
        minn=inf;
        for(int j=1; j<=n; ++j)
        {
            if(!vis[j]&&dis[j]<=minn)
                v=j,minn=dis[j];
        }
        vis[v]=true;
        for(int j=1; j<=n; ++j)
        {
            if(!vis[j])
                dis[j]=min(dis[j],dis[v]+G[v][j]);
        }
    }
}

O(eloge+n)
int dis[N],vis[N],V[N][N],m,n;
vector<int>G[N];
void dijkstra(int x)
{
    memset(dis,inf);
    dis[x]=0;
    priority_queue<pii>pri;
    pri.push({0,x});
    while(!pri.empty())
    {
        pii u=pri.top();
        pri.pop();
        if(vis[u.S])
            continue;
        vis[u.S]=true;
        for(auto v:G[u.S])
        {
            if(!vis[v]&&dis[u.S]+V[u.S][v]<dis[v])
            {
                dis[v]=dis[u.S]+V[u.S][v];
                pri.push({-dis[v],v});
            }
        }
    }
}

LCA

倍增LCA

第一步:dfs跑父结点(1倍祖先)-O(n)
第二步:预处理跑ST表-all倍增祖先- O(n*logn
第三步:LCA查询 单次O(logn) 多次O(mlogn

const int N=3e5+5;			///应用:1:两点之间的距离	2:最近公共祖先
int fat[N][21],h[N];
vector<int>G[N];
void dfs(int x)
{
    for(auto v:G[x])
    {
        if(v==fat[x][0])
            continue;
        h[v]=h[x]+1; fat[v][0]=x;
        dfs(v);
    }
}
int lca(int x,int y)
{
    if(h[x]<h[y]) swap(x,y);
    for(int i=20;i>=0;i--)
        if( (h[x]-h[y])>>i )
            x=fat[x][i];
    if(x==y)
        return x;
    for(int i=20;i>=0;i--)
        if( fat[x][i]!=fat[y][i] )
            x=fat[x][i],y=fat[y][i];
    return fat[x][0];
}
int dis(int x,int y)
{
    return h[x]+h[y]-h[lca(x,y)]*2;
}
for(int i=1;i<=20;i++)
        for(int j=1;j<=n;j++)
            fat[j][i]=fat[ fat[j][i-1] ][i-1];
       

倍增时间戳优化(链式前向星TLE再考虑,常数级优化)

const int N=5e5+5;
int in[N],out[N],fat[N][21],cnt=0,dis[N];
vector<int>G[N];
void dfs(int u)
{
    in[u]=++cnt;
    for(auto v:G[u])
    {
        if(v==fat[u][0])
            continue;
        dis[v]=dis[u]+1;
        fat[v][0]=u;
        for(int i=1;i<=20;i++)
            fat[v][i]=fat[fat[v][i-1]][i-1];
        dfs(v);
    }
    out[u]=++cnt;
}
bool ok(int u,int v)
{
    return in[u]<=in[v]&&out[v]<=out[u];
}
int lca(int u,int v)
{
    if(dis[u]>dis[v])
        swap(u,v);
    if(ok(u,v))
        return u;
    for(int i=20;i>=0;--i)
        if(!ok(fat[u][i],v)&&fat[u][i])
        u=fat[u][i];
    return fat[u][0];
}

树剖LCA

第一步:dfs跑Size depp fat -O(n)
第二步:dfs1跑top 轻重链-O(n)
第三步:LCA查询 单次O<=(logn) 比倍增更快
记住:卡常数老老实实链式前向星
Size 子树结点个数(包括本身) top 重链顶端结点
deep 结点的深度 fat父结点 son 当前结点的重儿子

const int N=5e5+5;
int Size[N],top[N],deep[N],fat[N],son[N];
vector<int>G[N];
void dfs(int u)
{
    Size[u]=1;
    deep[u]=deep[fat[u]]+1;
    for(auto v:G[u])
    {
        if(v==fat[u])
            continue;
        fat[v]=u;
        dfs(v);
        Size[u]+=Size[v];
        if(Size[v]>Size[son[u]])
            son[u]=v;
    }
}
void dfs1(int u)
{
    if(!top[u])
    top[u]=u;
    if(son[u])
    {
        top[son[u]]=top[u];
        dfs1(son[u]);
    }
    for(auto v:G[u])
        if(v!=fat[u]&&v!=son[u])
            dfs1(v);
}

int lca(int u,int v)
{
    while(top[u]!=top[v])
    {
        if(deep[top[u]]<deep[top[v]])
            swap(u,v);
        u=fat[top[u]];
    }
    return deep[u]<deep[v]?u:v;
}

二分图最大匹配

匈牙利算法O(VE)
HK算法O(sqrt(V)E)
KM算法
概念
1.最大独立点集:
在二分图中,选最多的点,使得任意两个点之间没有直接边连接。
最大独立集= 最小边覆盖 = 总点数- 最大匹配 (条件:在二分图中)

2.最小边覆盖:(最小不相交路径覆盖,u-v建边,add(u,v+n))
在二分图中,求最少的边,使得他们覆盖所有的点,并且每一个点只被一条边覆盖。
最小边覆盖=图中的顶点数-(最小点覆盖数)该二分图的最大匹配数(条件:在二分图中)
在这里插入图片描述

3.最小点覆盖:
在二分图中,求最少的点集,使得每一条边至少都有端点在这个点集中。
最小点覆盖 = 最大匹配 (条件:在二分图中)

匈牙利算法(核心:建图——>AC)
第一步:建立关系图,只需要构建一条边即可:
第二步:枚举每个男朋友
第三步:DFS寻找是否可以分配一个小老婆

时间复杂度:邻接矩阵O(最坏n^3) 邻接表O(n*m)
空间复杂度:邻接矩阵O(n^2) 邻接表O(n+m)

邻接矩阵(n^3)

const int N=1e3+5;
int G[N][N],vis[N],g[N],ans=0;///G存关系图	vis是否访问	g存妹子的对象,没有对象就0
bool find(int x,int n)
{
    for(int i=1;i<=n;i++)///扫描所有妹子
    {
        if(G[x][i]==true&&vis[i]==false)///A:是否有暧昧关系 B:是否访问
        {
            vis[i]=true;
            if(g[i]==0||find(g[i],n))///A:名花无主 B:能腾出位置,就递归
            {
                g[i]=x;
                return true;
            }
        }
    }
    return false;
}
for(int i=1; i<=p; i++)///跑一遍男孩子
{
    memset(vis,0);//每次找对象,要清空
    if(find(i,n))
        ans++;
}

邻接表(n*m)

const int N=3e3+5;///记得每次len ,g,G初始化
bool vis[N];
int g[N];
vector<int>G[N];
bool dfs(int u)
{
    for(auto v:G[u])
    {
        if(vis[v])
            continue;
        vis[v]=true;
        if(!g[v]||dfs(g[v]))
        {
            g[v]=u;
            return true;
        }
    }
    return false;
}

网络流

在普通情况下, DINIC算法时间复杂度为O(V2E)
在二分图中, DINIC算法时间复杂度为O(sqrt(V)E)

注意:初始化->head清空0,pos=1,还有s和t

namespace Dinic
{
    const int N=1e4+5;
    const int M=1e5+5;
    int s,t;
    int head[N*2],pos=1,deep[N*2];
    struct node
    {
        int next,to,w;
    }edge[M*6];
    void add(int from,int to,int w)
    {
        edge[++pos]={head[from],to,w};
        head[from]=pos;
    }
    void fadd(int from,int to,int w)
    {
        add(from,to,w),add(to,from,0);
    }
    bool bfs()
    {
        memset(deep,0x3f);
        queue<int>que;
        deep[s]=1;
        que.push(s);
        while(!que.empty())
        {
            int f=que.front();
            que.pop();
            for(int i=head[f];i;i=edge[i].next)
            {
                int to=edge[i].to;
                if(edge[i].w>0&&deep[f]+1<deep[to])
                {
                    deep[to]=deep[f]+1;
                    que.push(to);
                    if(to==t)return true;
                }
            }
        }
        return false;
    }
    int dfs(int u,int flow)
    {
        if(u==t)return flow;
        int res=0;
        for(int i=head[u];i;i=edge[i].next)
        {
            int to=edge[i].to;
            if(edge[i].w<=0||deep[to]!=deep[u]+1)continue;
            int re=dfs(to,min(flow,edge[i].w));
            if(re>0)
            {
                flow-=re;res+=re;
                edge[i].w-=re;
                edge[i^1].w+=re;
                if (!flow)return res;
            }
            else
                deep[to]=-1;
        }
        return res;
    }
    int dinic()
    {
        int ans=0;
        while(bfs())
            ans+=dfs(s, inf);
        return ans;
    }
}

强连通分量——tarjan算法(有向图)

第一步:枚举所有dfn为0跑tarjan
第二步:模板tarjan内部缩点
第三步:重新构图DAG

const int N=2e5+5;
int dfn[N],low[N],pos=0,col[N],col_cnt=0,cnt[N];
stack<int>sta;
set<int>insta;
vector<int>G[N];
void tarjan(int u)
{
    dfn[u]=low[u]=++pos;
    sta.push(u);
    insta.insert(u);
    for(auto v:G[u])
    {
        if(!dfn[v])
        {
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(insta.count(v))
            low[u]=min(low[u],dfn[v]);
    }
    if(dfn[u]==low[u])
    {
        col_cnt++;
        int k;
        do
        {

            k=sta.top(),sta.pop();
            insta.erase(k);
            col[k]=col_cnt,cnt[col_cnt]++;
        }while(u!=k);
    }
}

树上启发式合并(dsu on tree)——O(nlogn)

第一步:dfs(1,0),构造重儿子
第二步:dfs2(1,0,1);暴力跑

const int N=1e5+5;
vector<int>G[N];
int Size[N],son[N],col[N],cnt[N];
int ans[N],sum=0,maxn=0;
void dfs(int u,int fat)///构造重儿子
{
    Size[u]++;
    for(auto v:G[u])
    {
        if(v==fat)
            continue;
        dfs(v,u);
        Size[u]+=Size[v];
        if(Size[v]>Size[son[u]])
            son[u]=v;
    }
}
void add(int u,int fat,int val,int tag)
{
    cnt[col[u]]+=val;
    if(cnt[col[u]]>maxn)
        sum=col[u],maxn=cnt[col[u]];
    else if(cnt[col[u]]==maxn)
        sum+=col[u];
    for(auto v:G[u])
    {
        if(v==fat||v==tag)
            continue;
        add(v,u,val,tag);
    }
}
void dfs2(int u,int fat,int opt)
{
    for(auto v:G[u])
    {
        if(v==fat||v==son[u])
            continue;
        dfs2(v,u,0);///暴力处理轻边
    }
    if(son[u])///处理重儿子subtree
        dfs2(son[u],u,1);
    add(u,fat,1,son[u]);///u的substree除了重儿子的substree
    ans[u]=sum;
    if(!opt)///清除所有subtree of u的信息
        add(u,fat,-1,0),sum=0,maxn=0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值