Algor之图论

引文

在这里插入图片描述

邻接矩阵

在这里插入图片描述

邻接表

在这里插入图片描述
P3916 图的遍历

bool vis[N];
vector<int> g[N];//邻接表存图
int maxr[N];


void dfs(int u,int d) {
    if(maxr[u]) return;
    maxr[u]=d;
    for(int i=0;i<g[u].size();i++)
        dfs(g[u][i],d);
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int n, m;
    cin>>n>>m;

    
    rep(i, 1, m) {
        int a, b;
        cin >> a >> b ;
        g[b].pb(a);//反向建边
    }

    per(i,n,1) dfs(i,i);

    rep(i,1,n) cout<<maxr[i]<<' ';
    
    

    return 0;
}

边集数组

在这里插入图片描述

链式向前星(网络流)

在这里插入图片描述

优点

功能包含邻接表和链式邻接表

struct edge {
    int v, w, ne;
};
edge e[N];
int h[N], idx;
bool vis[N];

void add(int a, int b, int c) {
    e[idx] = {b, c, h[a]};
    h[a] = idx++;
}

void dfs(int u) {
    vis[u] = true;  // 标记当前节点已访问
    for(int i = h[u]; ~i; i = e[i].ne) {
        int v = e[i].v, w = e[i].w;
        if(vis[v]) continue;  // 如果节点v已经访问过,跳过它
        printf("%d %d %d\n", u, v, w);
        dfs(v);  // 递归访问节点v
    }
}

int main() {
    int n, m;
    cin >> n >> m;
    ms(h, -1);
    ms(vis, false);
    rep(i, 1, m) {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c);
        add(b, a, c);
    }
    dfs(1);  // 从节点1开始进行DFS遍历
    return 0;
}

朴素DIjkstra

在这里插入图片描述
在这里插入图片描述
朴素版变形题

int n,m,s,t;
double d[N],e[N][N];//邻接矩阵
int vis[N];

void dijk(){
    ms(vis,0);
    d[s]=1;
    rep(i,1,n){
        int u=-1;
        rep(j,1,n)
            if(!vis[j] && (u==-1 || d[j]>d[u])) u=j;
        vis[u]=1;
        rep(j,1,n) d[j]=max(d[j],d[u]*e[u][j]);
    }

}

int main(){
    cin>>n>>m;

    while(m--){
        int a,b,c;
        cin>>a>>b>>c;
        double t=(100.0-c)/100;//! 100.0
        e[a][b]=e[b][a]=max(e[a][b],t);
    }
    cin>>s>>t;
    dijk();
    printf("%.8lf",100/d[t]);


    return 0;
}

堆优化Dijktra

4779 【模板】单源最短路径(标准版)

使用 priority_queue

struct edge{
    int v,w;
};

int n,m,s;
vector<edge> e[N];//邻接表存图
int d[N],vis[N];

void dijkstra(int s){
    ms(d,0x3f);
    d[s]=0;
    //小根堆
    //priority_queue <PII,vector <PII>,greater <PII>> q;
    priority_queue<PII> q;
    q.push({0,s});
    while(q.size()){
        int u=q.top().se;
        q.pop();
        if(vis[u]) continue;
        vis[u]=1;
        for(auto ed:e[u]){
            int v=ed.v,w=ed.w;
            if(d[v]>d[u]+w[i]){
                d[v]=d[u]+w[i];
                q.push({-d[v],v});
            }
        }
    }
}

int main(){
    cin>>n>>m>>s;

    while(m--){
        int u,v,w;
        cin>>u>>v>>w;
        e[u].pb({v,w});
    }
    dijkstra(s);
    rep(i,1,n) cout<<d[i]<<' ';

    return 0;
}

SPFA

bellman_Ford的优化

在这里插入图片描述

3385 【模板】负环

vector

int n,m,T;
struct edge{
    int v,w;
};
vector<edge> e[N];
int vis[N],d[N],cnt[N];

bool spfa(int s){
    ms(d,0x3f);
    ms(vis,0);
    ms(cnt,0);
    d[s]=0;
    vis[s]=1;
    queue<int> q;
    q.push(s);
    while(q.size()){
        int u=q.front();
        q.pop(); vis[u]=0;
        for(auto ed : e[u]){
            int v=ed.v,w=ed.w;
            if(d[v]>d[u]+w){
                d[v]=d[u]+w;
                cnt[v]=cnt[u]+1;
                if(cnt[v]>=n) return 1;
                if(!vis[v]){
                    q.push(v);
                    vis[v]=1;
                }
            }
        }
    }
    return 0;
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    cin>>T;
    while(T--){
        ms(e,0);//清空e的所有元素
        cin>>n>>m;
        rep(i,1,m){
            int a,b,c;
            cin>>a>>b>>c;
            e[a].pb({b,c});
            if(c>=0) e[b].pb({a,c});
        }
        printf(spfa(1)?"YES":"NO");
        puts("");
    }

    return 0;
}

链式前向星–>静态数组

int n,m,T;
int e[N],ne[N],h[N],w[N],idx;
int vis[N],d[N],cnt[N];

void add(int a,int b,int c){
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}

bool spfa(int s){
    ms(d,0x3f);
    ms(vis,0);
    ms(cnt,0);
    d[s]=0;
    queue<int> q;
    vis[s]=1;//打标记
    q.push(s);
    while(q.size()){
        int u=q.front();
        q.pop(); vis[u]=0;//去标记
        for(int i=h[u];~i;i=ne[i]){
            int v=e[i];
            if(d[v]>d[u]+w[i]){
                d[v]=d[u]+w[i];
                cnt[v]=cnt[u]+1;
                if(cnt[v]>=n) return 1;
                if(!vis[v]){
                    q.push(v);
                    vis[v]=1;
                }
            }
        }
    }
    return 0;
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    cin>>T;
    while(T--){
        ms(h,-1);
        idx=0;
        cin>>n>>m;
        rep(i,1,m){
            int a,b,c;
            cin>>a>>b>>c;
            add(a,b,c);
            if(c>=0) add(b,a,c);
        }
        printf(spfa(1)?"YES":"NO");
        puts("");
        
    }

    return 0;
}

Floyd

在这里插入图片描述
入门->模板题

P6175 无向图的最小环问题

int n,m,ans=1e8;//最多为1e8,不能为1e9

int d[N][N],w[N][N];

void floyd(){
    rep(k,1,n){
        for(int i=1;i<k;i++)
            for(int j=i+1;j<k;j++)
                ans=min(ans,d[i][j]+w[j][k]+w[k][i]);
        rep(i,1,n)
            rep(j,1,n)
                d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
    }
}

int main(){
    cin>>n>>m;

    rep(i,1,n)
        rep(j,1,n)
            if(i!=j) w[i][j]=1e8;

    while(m--){
        int a,b,c;
        cin>>a>>b>>c;
        w[a][b]=w[b][a]=c;
    }    
    memcpy(d,w,sizeof d);

    floyd();
    
    if(ans==1e8) cout<<"No solution.";
    else cout<<ans;

    return 0;
}

Prim算法求最小生成树

P3366 【模板】最小生成树

H e a p − P r i m \color{RED}{Heap-Prim} HeapPrim

int n,m,ans,cnt;
struct edge{
    int v,w;
};
vector<edge> e[N];
int d[N],vis[N];

bool prim(int s){
    ms(d,0x3f);
    d[s]=0;
    priority_queue<PII> q;
    q.push({0,s});
    while(q.size()){
        int u=q.top().se;
        q.pop();
        if(vis[u]) continue;
        vis[u]=1;
        ans+=d[u],cnt++;
        for(auto ed : e[u]){
            int v=ed.v,w=ed.w;
            if(d[v]>w){
                d[v]=w;
                q.push({-d[v],v});
            }
        }
    }
    return cnt==n;
}

int main(){
    cin>>n>>m;

    while(m--){
        int a,b,c;
        cin>>a>>b>>c;
        e[a].pb({b,c});
        e[b].pb({a,c});
    }

    if(!prim(1)) cout<<"orz";
    else cout<<ans;

    return 0;
}

Kruskal算法求最小生成树

  • 利用并查集求MST
  • 看到这个n-1发现两个算法的一个区别是找短边,一个找距离最近的点.
  • 简要思路:判断是否在同一个集合,如果不在,则加入到mst,且合并两个集合

在这里插入图片描述

LCA

倍增算法

1 < < 20 ≈ 1 e 6 \color{BLUE}{1<<20 ≈1e6} 1<<201e6

1、dep[u]:存u点的深度
2、fa[u][i]:存从u点向上跳 2 i 2^i 2i层的祖先节点
3、打表:倍增递推,从小到大枚举
4、查询:二进制拆分,从大到小枚举

时间复杂度: O ( ( n + m ) ⋅ l o g n ) O((n+m)\cdot logn) O((n+m)logn)

在这里插入图片描述

在这里插入图片描述
P3379 【模板】最近公共祖先(LCA)

int n,m,s;
int dep[N],fa[N][20];
vector<int> e[N];

void dfs(int u,int dad){
    dep[u]=dep[dad]+1;
    fa[u][0]=dad;

    rep(i,1,19)
        fa[u][i]=fa[fa[u][i-1]][i-1];
    
    for(int v : e[u])
        if(v!=dad) dfs(v,u);
}

int lca(int u,int v){
    if(dep[u]<dep[v]) swap(u,v);

    per(i,19,0)
        if(dep[fa[u][i]]>=dep[v])
            u=fa[u][i];
    if(u==v) return v;

    per(i,19,0)
        if(fa[u][i]!=fa[v][i]) 
            u=fa[u][i],v=fa[v][i];
    
    return fa[u][0];
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    cin>>n>>m>>s;//s表示树根节点
    n--;
    while(n--){
        int a,b;
        cin>>a>>b;
        e[a].pb(b);
        e[b].pb(a);
    }

    dfs(s,0);//s表示树根节点

    while(m--){
        int x,y;
        cin>>x>>y;
        cout<<lca(x,y)<<endl;

    }

    return 0;
}

Tarjan算法(塔杨)

在这里插入图片描述
在这里插入图片描述

结合dfs,遵循入、回、离原则,先路径压缩,再查询

在这里插入图片描述

int n,m,s;
vector<int> e[N];
vector<PII> query[N];
// vector<PII> cnt[N];
int fa[N],vis[N],ans[N];
int ax[N],bx[N];//不能直接询问

int find(int x){
    return fa[x]==x?x:fa[x]=find(fa[x]);
}

void dfs(int u){
    vis[u]=1;//标记x已访问
    for(auto v : e[u]){
        if(!vis[v]){
            dfs(v);
            fa[v]=u;//回到x时指向x
        }
    }
	
	//离开x时找LCA
    for(auto q : query[u]){
        int v=q.fi,i=q.se;
        if(vis[v]) ans[i]=find(v);
    }
}

int main(){
    cin>>n;

    rep(i,1,N) fa[i]=i;//注意是N,而不是n

    rep(i,1,n){
        int a,b;
        cin>>a>>b;
        if(b==-1) s=a;
        else{
            e[a].pb(b);
            e[b].pb(a);
        }
    }

    cin>>m;
    rep(i,1,m){
        int a,b;
        cin>>a>>b;
        ax[i]=a,bx[i]=b;
        query[a].pb({b,i});
        query[b].pb({a,i});
        // if(ans[i]==a) cout<<"1\n";
        // else if(ans[i]==b) cout<<"2\n";
        // else cout<<"0\n";

    }

    dfs(s);//查询之后再dfs
    // rep(i,1,m) cout<<ans[i]<<endl;
    rep(i,1,m){
        if(ans[i]==ax[i]) cout<<"1\n"; 
        else if(ans[i]==bx[i]) cout<<"2\n";
        else cout<<"0\n";
    }
    return 0;
}

树链剖分

在这里插入图片描述
在这里插入图片描述

重链剖分

在这里插入图片描述

void dfs1(int u,int f){
    fa[u]=f,dep[u]=dep[f]+1,siz[u]=1;
    for(int v:e[u]){
        if(v==f) continue;
        dfs1(v,u);
        siz[u]+=siz[v];
        if(siz[son[u]]<siz[v]) son[u]=v;
    }
}

void dfs2(int u,int tp){
    top[u]=tp,id[u]=++cnt,nw[cnt]=w[u];
    if(!son[u]) return;
    dfs2(son[u],tp);
    for(int v:e[u]){
        if(v==fa[u] || son[u]==v) continue;
        dfs2(v,v);
    }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
P3384 【模板】重链剖分/树链剖分

#define lc u<<1
#define rc u<<1|1

int w[N],n,m,p,s;
int fa[N],son[N],dep[N],siz[N];
int cnt,id[N],nw[N],top[N];
vector<int> e[N];
struct node{
    int l,r;
    ll add,sum;
}tr[N*4];

void dfs1(int u,int f){
    fa[u]=f,dep[u]=dep[f]+1,siz[u]=1;
    for(int v:e[u]){
        if(v==f) continue;//beware
        dfs1(v,u);
        siz[u]+=siz[v];
        if(siz[son[u]]<siz[v]) son[u]=v;
    }
}

void dfs2(int u,int tp){
    top[u]=tp,id[u]=++cnt,nw[cnt]=w[u];
    if(!son[u]) return;
    dfs2(son[u],tp);
    for(int v:e[u]){
        if(v==fa[u] || son[u]==v) continue;
        dfs2(v,v);
    }
}

void pushup(int u){
    tr[u].sum=tr[lc].sum+tr[rc].sum;
}

void pushdown(int u){
    if(tr[u].add){
        tr[lc].sum+=tr[u].add*(tr[lc].r-tr[lc].l+1);
        tr[rc].sum+=tr[u].add*(tr[rc].r-tr[rc].l+1);
        tr[lc].add+=tr[u].add;
        tr[rc].add+=tr[u].add;
        tr[u].add=0;
    }
}

void build(int u,int l,int r){
    tr[u]={l,r,0,nw[r]};//beware
    if(l==r) return;
    int mid=l+r>>1;
    build(lc,l,mid),build(rc,mid+1,r);
    pushup(u);
}

void update(int u,int l,int r,int k){
    if(tr[u].l>=l && tr[u].r<=r){
        tr[u].add+=k,tr[u].sum+=k*(tr[u].r-tr[u].l+1);
        return;
    }
    pushdown(u);
    int mid=tr[u].l+tr[u].r>>1;
    if(mid>=l) update(lc,l,r,k);
    if(mid<r) update(rc,l,r,k);
    pushup(u);
}

ll query(int u,int l,int r){
    if(tr[u].l>=l && tr[u].r<=r) return tr[u].sum;
    pushdown(u);
    int mid =tr[u].l+tr[u].r>>1;
    ll res=0;
    if(mid>=l) res+=query(lc,l,r);
    if(mid<r) res+=query(rc,l,r);
    return res;
}

void update_path(int u,int v,int k){
    while(top[u]!=top[v]){
        if(dep[top[u]]<dep[top[v]]) swap(u,v);
        update(1,id[top[u]],id[u],k);
        u=fa[top[u]];
    }
    if(dep[u]<dep[v]) swap(u,v);
    update(1,id[v],id[u],k);
}

void update_tree(int u,int k){//修改子树
    update(1,id[u],id[u]+siz[u]-1,k);
}


ll query_path(int u,int v){
    ll res=0;
    while(top[u]!=top[v]){
        if(dep[top[u]]<dep[top[v]]) swap(u,v);
        res+=query(1,id[top[u]],id[u]);
        u=fa[top[u]];
    }
    if(dep[u]<dep[v]) swap(u,v);
    res+=query(1,id[v],id[u]);//最后一段
    return res;
}

ll query_tree(int u){//查询子树
    return query(1,id[u],id[u]+siz[u]-1);
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin>>n>>m>>s>>p;

    rep(i,1,n) cin>>w[i];

    // n--;
    for(int i=0;i<n-1;i++){
        int a,b;
        cin>>a>>b;
        e[a].pb(b),e[b].pb(a);
    }   

    dfs1(s,0);
    dfs2(s,s);//把树拆成链
    build(1,1,n);//用链建`线段树`
    while(m--){
        int op,a,b,c;
        cin>>op;
        if(op==1){
            cin>>a>>b>>c;
            update_path(a,b,c);
        } 
        else if(op==2){
            cin>>a>>b;
            cout<<query_path(a,b)%p<<endl;
        }
        else if(op==3){
            cin>>a>>c;
            update_tree(a,c);
        }else{
            cin>>a;
            cout<<query_tree(a)%p<<endl;
        }
    }
    return 0;
}

树上差分

在这里插入图片描述
Q:如果初态各点的点权不为0,如何做?
在这里插入图片描述

int n,m,ans;
int e[N],ne[N],h[N],idx;
int dep[N],f[N][22],power[N];

void add(int a,int b){
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

void dfs(int u,int dad){
    dep[u]=dep[dad]+1;
    f[u][0]=dad;
    rep(i,1,19) f[u][i]=f[f[u][i-1]][i-1];

    for(int i=h[u];~i;i=ne[i]){
        int v=e[i];
        if(v!=dad) dfs(v,u);
    }
}

int lca(int u,int v){
    if(dep[u]<dep[v]) swap(u,v);

    per(i,19,0)
        if(dep[f[u][i]]>=dep[v])
            u=f[u][i];
    if(u==v) return u;
    per(i,19,0)
        if(f[u][i]!=f[v][i])
            u=f[u][i],v=f[v][i];
    return f[u][0];
}

void dfs2(int u,int  dad){
    for(int i=h[u];~i;i=ne[i]){
        int v=e[i];
        if(v!=dad){
            dfs2(v,u);
            power[u]+=power[v];
        }
    }
    ans=max(ans,power[u]);
}

int main(){
    ms(h,-1);
    cin>>n>>m;

    rep(i,1,n-1){
        int a,b;
        cin>>a>>b;
        add(a,b), add(b,a);
    }
    dfs(1,0);
    while(m--){
        int a,b;
        cin>>a>>b;
        int l=lca(a,b);
        ++power[a], ++power[b];//点:树上差分
        --power[l], --power[f[l][0]];
    }
    dfs2(1,0);
    cout<<ans;
    return 0;
}

暗之连锁—>边的差分

强连通分量 Tarjan 算法

入、回、离(与LCA有异曲同工之妙)

在这里插入图片描述
在这里插入图片描述
− − − − − − − − − − − − 比较 − − − − − − − − − − − − \color{orange}{------------比较------------} 比较
在这里插入图片描述
− − − − − − − − − − − − 重点 − − − − − − − − − − − − \color{orange}{------------重点------------} 重点
在这里插入图片描述

P2863 [USACO06JAN] The Cow Prom S

int n,m,ans;
vector<int> e[N];
int dfn[N],low[N],tot;
int stk[N],instk[N],top;
int scc[N],cnt,siz[N];

void tarjan(int x){
    //入x时,盖戳、入栈
    dfn[x]=low[x]=++tot;
    stk[++top]=x,instk[x]=1;

    for(int y : e[x]){
        if(!dfn[y]){
            tarjan(y);
            low[x]=min(low[x],low[y]);//回x时更新low
        }
        //若y已访问且在栈中
        if(instk[y]) low[x]=min(low[x],dfn[y]);
    }

    //离x时,收集SCC
    if(low[x]==dfn[x]){//若x是SCC的根
        int y;cnt++;
        do{
            y=stk[top--],instk[y]=0;
            scc[y]=cnt;//SCC编号
            siz[cnt]++;//SCC大小
        }while(x!=y);
    }
}

int main(){
    cin>>n>>m;
    while(m--){
        int a,b;
        cin>>a>>b;
        e[a].pb(b);
    }

    rep(i,1,n)
        if(!dfn[i])
            tarjan(i);
    rep(i,1,cnt)
        if(siz[i]>1) ans++;
    
    cout<<ans;
    return 0;
}

Tarjan SCC 缩点

在这里插入图片描述
时间复杂度: O ( N + M ) \color{RED}{O(N+M)} O(N+M)
P2812 校园网络

int n,a;
vector<int> e[N];
int dfn[N],low[N],tot;
int stk[N],instk[N],top;
int scc[N],cnt;
int din[N],dout[N];

void tarjan(int x){
    dfn[x]=low[x]=++tot;
    stk[++top]=x,instk[x]=1;
    for(int y : e[x]){
        if(!dfn[y]){
            tarjan(y);
            low[x]=min(low[x],low[y]);
        }
        else if(instk[y]) low[x]=min(low[x],dfn[y]);
    }

    if(dfn[x]==low[x]){
        int y;cnt++;
        do{
            y=stk[top--], instk[y]=0;
            scc[y]=cnt;
        }while(x!=y);
    }
}

int main(){
    cin>>n;
    rep(i,1,n)
        while(cin>>a, a)
            e[i].pb(a);

    rep(i,1,n)
        if(!dfn[i])
            tarjan(i);

    rep(x,1,n)
        for(int y : e[x]){
            if(scc[x]!=scc[y]){
                din[scc[y]]++;
                dout[scc[x]]++;
            }
        }
    
    int aa=0,bb=0;
    rep(i,1,cnt){
        if(!din[i]) aa++;
        if(!dout[i]) bb++;
    }

    cout<<aa<<endl;
    if(cnt==1) puts("0");
    else cout<<max(aa,bb)<<endl;

    return 0;
}

Tarjan 割点

在这里插入图片描述
在这里插入图片描述

黑色是时间戳,红色是追溯值

在这里插入图片描述

int n,m;
vector<int> e[N];
int dfn[N],low[N],tot;
int cnt[N],s;

void tarjan(int x){
    dfn[x]=low[x]=++tot;
    int son=0;
    for(int y : e[x]){
        if(!dfn[y]){
            tarjan(y);
            low[x]=min(low[x],low[y]);
            if(low[y]>=dfn[x]){
                son++;
                if(x!=s || son>1) cnt[x]=1;
            }
        }else low[x]=min(low[x],dfn[y]);
    }
}


int main(){
    cin>>n>>m;
    
    while(m--){
        int a,b;
        cin>>a>>b;
        e[a].pb(b);
        e[b].pb(a);
    }
    for(s=1;s<=n;s++)//s:全局变量
        if(!dfn[s])
            tarjan(s);
    
    int ans=0;
    rep(i,1,n)
        if(cnt[i]) ans++;

    cout<<ans<<endl;

    rep(i,1,n)
        if(cnt[i]) cout<<i<<' ';//注意审题
    return 0;
}

Tarjan 割边

在这里插入图片描述
在这里插入图片描述

int n,m;
struct edge
{
    int u,v;
};
vector<edge> e;
vector<int> h[N];//出边
int dfn[N],low[N],tot,cnt;
struct bright{int x,y;}bri[N];

void add(int a,int b){
    e.pb({a,b});
    h[a].pb(SZ(e)-1);
}

void tarjan(int x,int ed){
    dfn[x]=low[x]=++tot;
    for(int i=0;i<h[x].size();i++){
        int j=h[x][i],y=e[j].v;
        if(!dfn[y]){
            tarjan(y,j);
            low[x]=min(low[x],low[y]);
            if(low[y]>dfn[x]) bri[cnt++]={x,y};
        }
        //不是反边
        else if(j!=(ed^1)) low[x]=min(low[x],dfn[y]);
    }
}

bool cmp(bright a,bright b){
    if(a.x==b.x) return a.y<b.y;
    return a.x<b.x;
}

int main(){
    cin>>n>>m;
    while(m--){
        int a,b;
        cin>>a>>b;
        add(a,b);
        add(b,a);
    }

    rep(i,1,n)
        if(!dfn[i])
            tarjan(i,0);

    sort(bri,bri+cnt,cmp);
    for(int i=0;i<cnt;i++)
        cout<<bri[i].x<<' '<<bri[i].y<<endl;
    return 0;
}

Tarjan三大总结

在这里插入图片描述

在这里插入图片描述

Tarjan eDCC 缩点

在这里插入图片描述
在这里插入图片描述

int n,m;
int h[N],e[N],ne[N],idx;
int dfn[N],low[N],tot;
int stk[N],top;
int dcc[N],cnt;
int bri[N],d[N];

void add(int a,int b){
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

void tarjan(int x,int ed){
    dfn[x]=low[x]=++tot;
    stk[++top]=x;
    for(int i=h[x];~i;i=ne[i]){
        int y=e[i];
        if(!dfn[y]){
            tarjan(y,i);
            low[x]=min(low[x],low[y]);
            if(low[y]>dfn[x]) bri[i]=bri[i^1]=1;
        }else if(i!=(ed^1)) low[x]=min(low[x],dfn[y]);
    }

    if(low[x]==dfn[x]){
        ++cnt;
        int y;
        do{
            y=stk[top--];
            dcc[y]=cnt;
        }while(x!=y);
    }
}


int main(){
    ms(h,-1);
    cin>>n>>m;
    while(m--){
        int a,b;
        cin>>a>>b;
        add(a,b),add(b,a);
    }

    tarjan(1,-1);//题目条件是连通图

    for(int i=0;i<idx;i++)//边号
        if(bri[i])
            d[dcc[e[i]]]++;//度数

    int sum=0;
    rep(i,1,cnt)
    	//叶子节点
        if(d[i]==1) sum++;

    cout<<(sum+1)/2;
    return 0;
}

Tarjan vDCC 缩点

在这里插入图片描述

缩点总结

在这里插入图片描述

在这里插入图片描述

网络流 最大流 EK 算法

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

int n,m,u,S,T;
int h[N],e[N],ne[N],idx;
ll mf[N],pre[N],cp[N];

void add(int a,int b,int c){
    //cp:存储容量capaci-->W[]
    e[idx]=b,cp[idx]=c,ne[idx]=h[a],h[a]=idx++;
}

bool bfs(){//找增广路
    ms(mf,0);
    queue<int> q;
    q.push(S); mf[S]=1e9;
    while(SZ(q)){
        int u=q.front(); q.pop();
        for(int i=h[u];~i;i=ne[i]){
            ll v=e[i];
            if(mf[v]==0 && cp[i]){
                mf[v]=min(mf[u],cp[i]);//必须同一数据类型
                pre[v]=i;//存前驱边
                q.push(v);
                if(v==T) return 1;
            }
        }
    }
    return 0;
}

ll EK(){//累加可行流
    ll flow=0;
    while(bfs()){
        int v=T;
        while(S!=v){//更新残留网
            int i=pre[v];
            cp[i]-=mf[T];
            cp[i^1]+=mf[T];
            v=e[i^1];
        }
        flow+=mf[T];
    }
    return flow;
}

int main(){
    cin>>n>>m>>S>>T;

    ms(h,-1);//-1:boundary

    while(m--){
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c), add(b,a,0);//反向边
    }

    cout<<EK();
    
    return 0;
}

网络流 最大流 Dinic 算法

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

网络流 最小割 Dinic 算法

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

网络流 费用流 EK 算法

在这里插入图片描述
在这里插入图片描述

int n,m,S,T;
int h[N],w[N],e[N],ne[N],idx;
ll cp[N],mf[N],d[N],vis[N],pre[N];
ll flow,cost;

void add(int a,int b,int c,int d){
    e[idx]=b,cp[idx]=c,w[idx]=d,ne[idx]=h[a],h[a]=idx++;
}

bool spfa(){//最短路找增广路
    ms(d,0x3f);
    ms(mf,0);
    queue<int> q;
    q.push(S);
    d[S]=0,mf[S]=INF,vis[S]=1;
    while(SZ(q)){
        int u=q.front(); q.pop(); vis[u]=0;
        for(int i=h[u];~i;i=ne[i]){
            ll v=e[i], c=cp[i], we=w[i];
            if(d[v]>d[u]+we && c){
                d[v]=d[u]+we;//最短路
                mf[v]=min(mf[u],c);
                pre[v]=i;
                if(!vis[v]){
                    q.push(v);
                    vis[v]=1;
                }
            }
        }
        
    }
    return mf[T]>0;
}

void EK(){
    while(spfa()){
        for(int v=T;S!=v;){
            int i=pre[v];
            cp[i]-=mf[T];
            cp[i^1]+=mf[T];
            v=e[i^1];
        }
        flow+=mf[T];//累加可行流
        cost+=mf[T]*d[T];//累加费用  
    }
}

int main(){
    ms(h,-1);
    cin>>n>>m>>S>>T;

    while(m--){
        int a,b,c,d;
        cin>>a>>b>>c>>d;
        add(a,b,c,d);
        add(b,a,0,-d);
    }

    EK();
    cout<<flow<<' '<<cost;
    return 0;
}

二分图判定 染色法

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

int n,m;
int h[N],e[N],ne[N],idx;
int flag,color[N];

void add(int a,int b){
	e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

bool dfs(int u,int c){
	color[u]=c;
	for(int i=h[u];~i;i=ne[i]){
		int v=e[i];
		if(!color[v]){//这里一定要加个{}
			if(dfs(v,3-c)) return 1;
		}
		else if(color[v]==c) return 1; 
	}
	return 0;
}

int main(){
	ms(h,-1);

	cin>>n>>m;
	while(m--){
		int a,b;
		cin>>a>>b;
		add(a,b),add(b,a);
	}

	rep(i,1,n){
		if(!color[i])
			if(dfs(i,1)){
				flag=1;
				break;
			}
	}

	if(flag) cout<<"No";
	else cout<<"Yes";
}	

二分图最大匹配 匈牙利算法

在这里插入图片描述
在这里插入图片描述

int n,m,a,b,k,ans;
int h[N],e[N],ne[N],idx;
int match[N],vis[N];

void add(int a,int b){
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

bool dfs(int u){
    for(int i=h[u];~i;i=ne[i]){
        int v=e[i];
        if(vis[v]) continue;
        vis[v]=1;
        if(!match[v] || dfs(match[v])){
            match[v]=u;
            return 1;
        }
    }
    return 0;
}

int main(){
    ms(h,-1);
    cin>>n>>m>>k;

    while(k--){
        cin>>a>>b;
        
        add(a,b);
    }

    rep(i,1,n){
        ms(vis,0);
        if(dfs(i)) ans++;
    }
    cout<<ans;

    return 0;
}

二分图最大匹配 Dinic算法

在这里插入图片描述
在这里插入图片描述

二分图最大权完美匹配 KM算法

  • 匈牙利算法演化而来
  • 思考:如何优化dfsbfs,使得复杂度降为 0 ( n 3 ) 0(n^3) 0(n3)

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

基环树

在这里插入图片描述
在这里插入图片描述
骑士
在这里插入图片描述

int n,m,r1,r2;
int e[N],ne[N],h[N],idx;
int w[N],vis[N];
ll f[N][2];

void add(int a,int b){
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

void find(int u,int rt){//找两个根
    vis[u]=1;
    for(int i=h[u];~i;i=ne[i]){
        int v=e[i];
        if(v==rt){r1=u,r2=v;return;}
        if(vis[v]) continue;
        find(v,rt);
    }
}

ll dfs(int u,int rt){//树上DP
    f[u][0]=0, f[u][1]=w[u];
    for(int i=h[u];~i;i=ne[i]){
        int v=e[i];
        if(v==rt) continue;
        dfs(v,rt);
        f[u][0]+=max(f[v][0],f[v][1]);
        f[u][1]+=f[v][0];
    }
    return f[u][0];

}


int main(){
    ms(h,-1);
    cin>>n;
    for(int i=1,u;i<=n;i++){
        cin>>w[i]>>u;
        add(u,i);
    }

    ll sum=0;
    rep(i,1,n){
        if(!vis[i]){
            r1=r2=0;//环上的起点和终点
            find(i,i);
            if(r1){
                ll res1=dfs(r1,r1);
                ll res2=dfs(r2,r2);
                sum+=max(res1,res2);
            }
        }
    }
    cout<<sum;

    return 0;
}

旅行
在这里插入图片描述

int n,m;
vector<int> e[N];
vector<int> path(N,N);//
PII edge[N];
int du,dv,vis[N];//断边的两个点
int cnt,bt;

bool dfs(int u){
    if(!bt){
        if(u>path[cnt]) return 1;
        if(u<path[cnt]) bt=-1; 
    }
    vis[u]=1;
    path[cnt++]=u;
    for(auto x : e[u]){
        int v=x;
        if(vis[v]) continue;
        if(du==u && dv==v) continue;
        if(du==v && dv==u) continue;
        if(dfs(v)) return 1; 
    }
    return 0;
}

int main(){
    cin>>n>>m;

    rep(i,1,m){
        int a,b;
        cin>>a>>b;
        e[a].eb(b);
        e[b].eb(a);
        edge[i]={a,b};
    }

    rep(i,1,n) sort(all(e[i]));

    if(n==m+1) dfs(1);
    else{
        rep(i,1,m){
            du=edge[i].fi;
            dv=edge[i].se;
            ms(vis,0);
            cnt=bt=0;
            dfs(1);
        }
    }

    rep(i,0,n-1) cout<<path[i]<<' ';

    return 0;
}

圆方树 【模板】静态仙人掌

仙人掌: 环与环之间不存在公共边

在这里插入图片描述

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值