ACM图论学习笔记

14 篇文章 0 订阅
3 篇文章 0 订阅

图论

链式向前星

struct LS{
    int cnt=0;
    struct cpp {
        int to,cost,next;
    }edge[N*2];
    int head[ N ];
    void inta(int n){
        cnt=0;
        memset( head,0,sizeof(int)*(n+3) );
    }
    void addEdge(int from,int to,int cost){
        ++cnt;
        edge[cnt].to = to;
        edge[cnt].cost = cost;
        edge[cnt].next = head[from];
        head[from] =cnt;
    }
};
int main(){
    int x=1;
    for(int i=head[x];i!=0;i=edge[i].next){//遍历

    }
    return 0;
}

最短路

floyed

void floyed(){

    for(int i=1;i<=n;i++){//初始化
        for(int j=1;j<=n;j++){
            dis[i][j]=1e9;
            if(i==j) dis[i][j]=0;
        }
    }
    
    for(int i=1,a,b,cost;i<=m;i++){
        cin>>a>>b>>cost;
        dis[a][b]=dis[b][a]=cost;
        path[a][b]=b;
        path[b][a]=a;
    }
	//核心代码
    for(int k=1;k<=n;k++){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                if( dis[i][j]>dis[i][k]+dis[k][j] ){
                    dis[i][j]=dis[i][k]+dis[k][j];
                    path[i][j]=path[i][k];//路径
                }else if(dis[i][j]==dis[i][k]+dis[k][j]+b[k] && path[i][j]>path[i][k]){//字典序最小路径
                    path[i][j]=path[i][k];
                }
            }
        }
    }
	//路径打印    
    int s,g;
    for(int i=s;i!=g;i=path[i][g])
        printf("%d ",i);
    printf("%d",g);

}

floyed 求最小环

a n s = f k − 1 ( i , j ) + Graph ⁡ [ i ] [ k ] + Graph ⁡ [ j ] [ k ] a n s=f^{k-1}(i, j)+\operatorname{Graph}[i][k]+\operatorname{Graph}[j][k] ans=fk1(i,j)+Graph[i][k]+Graph[j][k]

最小三条边的环

void folyed(){
    rep(k,1,n){
        rep(i,1,k-1){
            rep(j,i+1,k-1){
                if(ans>dis[i][j]+cost[i][k]+cost[j][k]){
                    ans=dis[i][j]+cost[i][k]+cost[j][k];
                }
            }
        }
        rep(i,1,n){
            rep(j,1,n){
                if(dis[i][j]>dis[i][k]+dis[k][j]){
                    dis[i][j]=dis[i][k]+dis[k][j];
                }
            }
        }
    }
}

Dijkstra

struct cpp{
    int to,cost;
}tem;
bool vis[10005];
int dis[10005];
int path[10005];
priority_queue<cpp> dui;
bool operator<(const cpp&a,const cpp&b){
    return a.cost>b.cost;
}
vector<cpp> g[10005];
int n,s;
void Dijkstra(){
    rep(i,1,n) dis[i]=1e9;
    dis[s]=0;
    tem.cost=0;tem.to=s;
    dui.push(tem);
    int u;
    while(!dui.empty()){
        u=dui.top().to;
        dui.pop();
        if(vis[u]) continue;
        vis[u]=1;
        rep(i,0, (int)(g[u].size()-1) ){
            if( vis[g[u][i].to]==0&&dis[g[u][i].to]>dis[u]+g[u][i].cost ){
                dis[g[u][i].to]=dis[u]+g[u][i].cost;
                path[g[u][i].to]=u;//更新路径
                tem.to=g[u][i].to;tem.cost=dis[g[u][i].to];
                dui.push(tem);
            }
        }
    }
}

void print_path(int ed){
    stack<int> mystack;
    mystack.push(ed);
    while(ed!=s){
        ed=path[ed];
        mystack.push(ed);
    }
    while(!mystack.empty()){
        cout<<mystack.top()<<" ";
        mystack.pop();
    }
    putchar('\n');
}
vector< pair<double,int> > g[N];
bool vis[N];
double dis[N];
priority_queue< pair<double,int>,vector<pair<double,int>>,greater< pair<double,int> > > dui;
void Dijkstra(int s){
    for(int i=1;i<=n;i++) dis[i]=1e18,vis[i]=0;
    dis[s]=0.0; dui.push( make_pair(0.0,s) );
    int u;
    while( dui.size() ){
        u=dui.top().second;
        dui.pop();
        if( vis[u] ) continue;
        vis[u]=1;
        for(auto [y,x]:g[u]){
            if( vis[x]==0 && dis[x]>dis[u]+y ){
                dis[x]=dis[u]+y;
                dui.push( make_pair( dis[x] ,x ) );
            }
        }
    }
}

Bellman_ford

核心思想 : 俩点之间最多有n-1条边

struct cpp{
    int from,to,cost;//如果是无向图,记得存from和to交换位置再存一遍
}edge[500005];
int dis[10005];
int path[10005];//路径打印
int n,s,m;
void Bellman_ford(){
    rep(i,1,n) dis[i]=1e9;
    dis[s]=0;
    bool flag;
    rep(i,1,n-1){
        flag=1;
        rep(j,1,m){
            if(dis[edge[j].to]>dis[edge[j].from]+edge[j].cost){
                dis[edge[j].to]=dis[edge[j].from]+edge[j].cost;
                path[edge[j].to]=edge[j].from;//更新路径
                flag=0;
            }
        }
        if(flag) break;
    }
}
void print_path(int ed){//打印路径
    stack<int> mystack;
    mystack.push(ed);
    while(ed!=s){
        ed=path[ed];
        mystack.push(ed);
    }
    while(!mystack.empty()){
        cout<<mystack.top()<<" ";
        mystack.pop();
    }
    putchar('\n');
}

SPFA

如果要判负环,统计一个点的入队次数就行了,入队次数大于n则存在负环

int n,s;
int dis[10005];
int path[10005];
bool vis[10005];
struct cpp{
    int to,cost;
}tem;
vector<cpp> g[10005];
queue<int> dui;

void SPFA(){
    repi(i,1,n) dis[i]=2147483647;
    dis[s]=0; vis[s]=1; dui.push(s);
    while(!dui.empty() ){
        int x=dui.front();dui.pop();
        vis[x]=0;
        repi(i,0, (int) (g[x].size()-1) ){
            if( dis[ g[x][i].to ]>dis[ x ] + g[x][i].cost ){
                dis[ g[x][i].to ]=dis[ x ] + g[x][i].cost;
                path[ g[x][i].to ]=x;//更新路径
                if(vis[ g[x][i].to ]==0){
                    vis[ g[x][i].to ]=1;
                    dui.push(g[x][i].to);
                }
            }
        }
    }
}
void print_path(int ed){//打印路径
    stack<int> mystack;
    mystack.push(ed);
    while(ed!=s){
        ed=path[ed];
        mystack.push(ed);
    }
    while(!mystack.empty()){
        cout<<mystack.top()<<" ";
        mystack.pop();
    }
    putchar('\n');
}

差分约束

差分约束 - Kersen - 博客园 (cnblogs.com)

最小生成树

Kruskal算法

int fa[10005];
inline int find(int u){
    if ( 0==fa[u]) return fa[u]=u;
    int x=u,t;
    while(x!=fa[x]) x=fa[x];
    while(u!=x){ t=fa[u];fa[u]=x;u=t; }
    return x;
}
inline void un(int u,int v){
    fa[find(u)]=find(v);
}
inline bool same_r(int u,int v){
    return find(u)==find(v);
}
struct cpp{
    int a,b,c;
}edge[200005];
bool cmp(cpp &x,cpp &y){
    return x.c<y.c;
}
int Kruskal( int id,int len,int ge ){//边开始下标,总边个数,需要生成边个数
    sort( edge+id,edge+id+len,cmp );
    int ans=0;
    repi(i,0,len-1){
        if( !same_r( edge[i].a,edge[i].b ) ){
            un( edge[i].a,edge[i].b );
            ans+=edge[i].c;
            ge--;
            if(0==ge) return ans;
        }
    }
    return -1;
}
int ans;
int n,m;
int main(){
    scanf("%d %d",&n,&m);
    for(int i=0;i<m;i++)
        scanf("%d %d %d",&edge[i].a,&edge[i].b,&edge[i].c);
    cout<<Kruskal( 0,m,n-1 );
    return 0;
}

Prim算法

T(n^2)
struct cpp{
    int x,cost;//到达的点和消耗
}tem;
vector<cpp> g[5005];
bool vis[5005];
int num=1;
int dis[5005];
int n,m;

inline int prim(){
    int sum=0,min_cost,cnt=0;
    while(++cnt<n){
        min_cost=2e9;
        for(unsigned int i=0;i<g[num].size();i++){
            dis[g[num][i].x]=min(dis[g[num][i].x],g[num][i].cost);
        }
        vis[num]=1;
        for(int i=1;i<=n;i++){
            if(vis[i]==0 && min_cost>dis[i]){
                num=i;
                min_cost=dis[i];
            }
        }
        sum+=min_cost;
    }
    return sum;
}

int main(){
    cin>>n>>m;
    for(int i=2;i<=n;i++)
    dis[i]=2e9;
    for(int i=0,a;i<m;i++){
        cin>>a>>tem.x>>tem.cost;
        g[a].push_back(tem);
        swap(a,tem.x);
        g[a].push_back(tem);
    }
    cout<<prim();
    return 0;
}
次小生成树

核心思想 : 枚举最小生成树总未被利用的边 , 会生成一个环 , 删除环中的除自己以外的最大边;

新树权值=MST权值+枚举边权值 - 环最大权值

次小生成树权值=所有新树的的最小权值树

Kruskal重构树

性质:

这是一个二叉大根堆

原树两点之间的边权最大值是重构树上两点Lca的权值

重构树中代表原树中的点的节点全是叶子节点,其余节点都代表了一条边的边权

ll a[N];
struct E{
    int u,v; ll w;
}e[N];
vector<int> g[N];
void Ex_Kruskal(){
    int id=n;
    sort(e+1,e+1+m,[](const E &A,const E &B){
        return A.w<B.w;
    });
    for(int i=1;i<=n*2;i++) fa[i]=i;
    for(int i=1,u,v;i<=m;i++){
        u=find(e[i].u); v=find(e[i].v);
        if(u==v) continue;
        fa[u]=fa[v]=++id;
        a[id]=e[i].w;//边权变点权
        g[id].push_back(u);
        g[id].push_back(v);
        g[u].push_back(id);
        g[v].push_back(id);
        if(id==2*n-1) break;
    }
}

二分图

二分图的判定 : 染色法

匈牙利
int n,m,e;
vector<int> g[N];
bool vis[N];
int nxt[N];

bool Find(int id){
    for( auto x:g[id] ){
        if( vis[x] ) continue;
        vis[x] = 1;
        if( nxt[x]==0 || Find(nxt[x]) ){
            nxt[x] = id;
            return true;
        }
    }
    return false;
}

int match(){
    int sum=0;
    for(int i=1;i<=n;++i){
        ms(vis,0);
        if( Find(i) ) ++sum;
    }
    return sum;
}

int main(){
    intxt();
    cin>>n>>m>>e;
    int u,v;
    rep(i,1,e){
        cin>>u>>v;
        g[u].pb(v);
    }
    cout<<match()<<endl;
    return 0;
}

网络流

Dinic
const int N=2e2+15;//点数
const int M=5e3+15;//边数

struct DINIC{
    int n,m,s,t;
    ll tot=0;
    bool vis[N];
    int dep[N],q[N<<1],cur[N],l,r;

struct LS{
    int cnt=1;
    int to[M<<1],nxt[M<<1];
    ll val[M<<1];
    int head[N];
    void inta(int n){
        cnt = 1;
        memset( head,0,sizeof(int)*(n+3) );
    }
    void addEdge(int u,int v,ll w){
        ++cnt;
        to[cnt] = v;
        val[cnt] = w;
        nxt[cnt] = head[u];
        head[u] = cnt;
    }
}G;

    bool bfs(){
        memset(dep , 0 , sizeof(int)*(n+2) );
        q[l=r=1] = s;
        dep[s] = 1;
        while(l<=r){//队列
            int  u = q[l++];
            for(int p=G.head[u];p;p=G.nxt[p]){
                int v = G.to[p];
                if( !G.val[p] || dep[v] )
                    continue;
                dep[v] = dep[u] + 1;
                q[++r] = v;
            }
        }
        return dep[t]!=0;
    }

    ll dfs(int u,ll in){
        if(u==t) return in;
        ll out = 0;
        for(int &p=cur[u]; p&&in ;p=G.nxt[p]){
            int v = G.to[p];
            if( dep[v]==dep[u]+1 && G.val[p]!=0LL ){
                ll res = dfs( v,min( G.val[p],in ) );
                G.val[p] -= res;
                G.val[p^1] += res;
                in -= res;
                out += res;
                if( in==0LL ) return out;
            }
        }
        if(out == 0LL)
            dep[u] = 0LL;
        return out;
    }
    ll Dinic(){
        scanf("%d%d%d%d",&n,&m,&s,&t);
        G.inta(n);
        int u,v;ll w;
        while(m--){
            scanf("%d%d%lld",&u,&v,&w);
            G.addEdge(u,v,w);
            G.addEdge(v,u,0LL);
        }
        while(bfs()){
            memcpy(cur, G.head, sizeof(int)*(n+2) );
            tot += dfs(s,1e18);
        }
        return tot;
    }
}D;

int main(){
    intxt();
    printf("%lld\n",D.Dinic());
    return 0;
}

结论

七桥问题

图是否可以从某点开始经过每一条边准确一次 : (边全联通)只要满足图每个点的度全为偶或者刚好两个奇则满足

其中全为偶时 , 每个点都可以作为起点 , 起点就是终点

刚好两个奇时 , 两个奇分别为起点或终点

DFS序

每个节点在dfs深度优先遍历中的进出栈的时间序列。

定义两个数组,in[x],out[x]。dfs从根结点开始,每个结点分别记录两个信息:in[x],out[x],in[x]为dfs进入结点x时的时间戳,out[x]为dfs离开结点x时的时间戳。

那么节点x可以管辖到的子树范围就是[ in[x] , out[x] ] , 这是就可以用维护区间的数据结构来操作了 , 线段树,树状数组等

int time=0;
int in[maxn];
int out[maxn];
void dfs( int id ){
    in[id]=++time;
    for(int i=head[id];i!=-1;i=edge[i].next){
        dfs( edge[i].to );
    }
    out[id]=time;
}

树的直径

树的直径 : 树中距离最远的两个点

求法 : 跑两遍bfs或者dfs即可

​ 1,任取一个点,找到离这这个点最远的节点

​ 2,求该节点求到其他点的距离,其中的最长距离就是树的直径

树的重心

树的重心定义为树的某个节点,当去掉该节点后,树的各个连通分量中,节点数最多的连通分量其节点数达到最小值。性质 : 树中所有点到某个点的距离和中,到重心的距离和是最小的;如果有两个重心,那么到它们的距离和一样。在一棵树上添加或删除一个叶子,那么它的重心最多只移动一条边的距离。

int weigh[maxn];//weight最小的是树的重心
bool vis[maxn];
int dfs( int id ){
    int res=0,sum=1,tem;
    for(int i=head[id];i!=-1;i=edge[i].next){
        if( vis[ edge[i].to ]==0 ){
            vis[ edge[i].to ]=1;
            tem=dfs( edge[i].to );
            res=max( tem,res );
            sum+=tem;
        }
    }
    weigh[id]=max( res,n-sum );
    return sum;
}

最近公共祖先LCA

树剖法
struct LS{
    int cnt=0;
    struct cpp {
        int to,cost,next;
    }edge[N*2];
    int head[ N ];
    void inta(){
        cnt=0;
        memset( head,-1,sizeof(head) );
    }
    void addEdge(int from,int to,int cost){
        ++cnt;
        edge[cnt].to = to;
        edge[cnt].cost = cost;
        edge[cnt].next = head[from];
        head[from] =cnt;
    }
}G;

int n,m,root;
struct T{
    int fa,dep,size,son,top;
}node[N];

void dfs1(int u,int f,int depth){
    node[u].fa = f ;
    node[u].dep = depth ;
    node[u].size = 1 ;
    for(int i=G.head[u];i!=-1;i=G.edge[i].next){
        int v=G.edge[i].to;
        if(v==f) continue;
        dfs1(v,u,depth+1);
        node[u].size += node[v].size;
        if( node[v].size > node[node[u].son].size ) node[u].son = v;
    }
}

void dfs2(int u,int t){
    node[u].top = t ;
    if( !node[u].son ) return;
    dfs2( node[u].son,t );
    for(int i=G.head[u];i!=-1;i=G.edge[i].next){
        int v=G.edge[i].to;
        if( v!=node[u].son && v!=node[u].fa )
        dfs2(v,v);
    }
}

int lca_query(int x,int y){
    while( node[x].top!=node[y].top ){
        if( node[ node[x].top ].dep < node[ node[y].top ].dep ){
            swap(x,y);
        }
        x = node[ node[ x ].top ].fa ;
    }
    return node[x].dep<node[y].dep?x:y;
}

int main(){
    intxt();
    read(n);read(m);read(root);
    G.inta();
    int u,v;
    rep(i,1,n-1){
        read(u);read(v);
        G.addEdge(u,v,0);
        G.addEdge(v,u,0);
    }

    dfs1(root,-1,1);
    dfs2(root,root);

    rep(i,1,m){
        read(u);read(v);
        printf("%d\n",lca_query( u,v ));
    }
    return 0;
}
倍增法
int n,s;
int depth[N];
int anc[N][30];

void dfs(int u,int fa){//深搜出各点的深度 , 存在depth中
    anc[u][0]=fa;
    for(int i=head[u];i!=-1;i=edge[i].next){//遍历
        if( edge[i].to==fa ) continue;
        depth[edge[i].to] = depth[u] + 1 ;
        dfs(edge[i].to,u);
    }
}

void bz(){//倍增,处理anc数组
    int maxdep=int(log(n)/log(2) + 1);

    for(int j=1;j<=maxdep;j++){
        for(int i=1;i<=n;i++){//i = 点下标 , n为点结束
            anc[i][j] = anc[ anc[i][j-1] ][j-1];//初始化全部赋值-1
        }
    }
}
int lca_query(int u,int v){//询问最近公共祖先
    if( depth[u]<depth[v] ) swap(u,v);

    int logsn = int( log(depth[u])/log(2) + 1);
    for(int i = logsn;i>=0;i--){
        if( depth[u] - (1<<i)>=depth[v] )
            u = anc[u][i];
    }
    if(u==v) return u;
    for(int i=logsn;i>=0;i--){
        if( anc[u][i]!=-1 && anc[u][i]!=anc[v][i] ){
            u = anc[u][i];
            v = anc[v][i];
        }
    }
    return anc[u][0];
}
void init(){
    memset(anc,-1,sizeof(anc));
    dfs(s,-1);//s为根
    bz();
}
树上差分

点差分

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zk98cqPv-1650207058469)(D:\makedown\ACM_doc\图论\点差分.png)]

点u,v之间的值+val , 那么cnt[u]+=val , cnt[v]+=val , cnt[LCA]-=val , cnt[ fa[LCA] ]-=val;

最后dfs回溯就行num[u]=cnt[u] + all_son( num[] )

边差分

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QjSF3EuL-1650207058470)(D:\makedown\ACM_doc\图论\边差分.png)]

把边塞给点的话,是塞给这条边所连的深度较深的节点. (即塞给儿子节点

int cnt[N],num[N];	
void dfs1(int u,int fa,int &ans){
   	num[u] = cnt[u];
    for(auto x:g[u]){
        if( x.to==fa ) continue;
        dfs1(x.to,u,ans);
        num[u]+=num[x.to];
    }
tarjan

缩点

int n,m,cnt_dfn,cnt_sc;
vector<int> g[N],scg[N];
int dfn[N],low[N],sc_id[N],sc_nums[N];
bool in_sta[N];
stack<int> sta;

void tarjan(int u,int f){
    low[u]=dfn[u]=++cnt_dfn;
    sta.push(u);  in_sta[u]=1;
    for(auto x:g[u]){
        if(dfn[x]==0){
            tarjan(x,u);
            low[u]=min(low[u],low[x]);
        }else if(in_sta[x]==1){
            low[u]=min(low[u],dfn[x]);
        }
    }
    if( dfn[u]==low[u] ){
        ++cnt_sc;
        while( sta.top()!=u ){
            sc_id[sta.top()]=cnt_sc;
            sc_nums[cnt_sc]++;
            in_sta[sta.top()]=0;
            sta.pop();
        }
        sc_id[sta.top()]=cnt_sc;
        sc_nums[cnt_sc]++;
        in_sta[sta.top()]=0;
        sta.pop();
    }
}
//建立缩点新图
void creat_new_g(){
    for(int i=1;i<=n;i++){
        if( dfn[i]==0 ) tarjan(i,i);
    }
    for(int i=1;i<=n;i++){
        for(auto x:g[i]){
            if( sc_id[i]==sc_id[x] ) continue;
            scg[ sc_id[i] ].push_back( sc_id[x] );
        }
    }
    for(int i=1;i<=cnt_sc;i++){
        sort( scg[i].begin(),scg[i].end() );
        scg[i].erase( unique( scg[i].begin(),scg[i].end() ),scg[i].end() );
    }
}

求割点

int n,m,cnt_dfn;
int low[N],dfn[N];
bool vis[N];
vector<int> g[N];

void tarjan(int u,int root){
    low[u]=dfn[u]=++cnt_dfn;
    int cnt_son=0;
    for(auto x:g[u]){
        if(low[x]==0){
            tarjan(x,root);
            low[u]=min(low[u],low[x]);
            if(low[x]>=dfn[u]&&u!=root) vis[u]=1;
            if(u==root) cnt_son++;
        }
        low[u]=min(low[u],dfn[x]);
    }
    if(cnt_son>=2&&u==root){
        vis[u]=1;
    }
}

int main(){
    cin>>n>>m;
    for(int i=1,u,v;i<=m;i++){
        cin>>u>>v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    for(int i=1;i<=n;i++){
        if(dfn[i]==0) tarjan(i,i);
    }
    int ans=0;
    for(int i=1;i<=n;i++){
        if(vis[i]==1) ++ans;
    }
    cout<<ans<<endl;
    for(int i=1;i<=n;i++){
        if(vis[i]) cout<<i<<" ";
    }
    return 0;
}

求割边(桥)

int n,m,cnt_dfn;
vector<int> g[N];
int dfn[N],low[N];
vector<pair<int,int>> bridges;

void tarjan(int u,int f){
    low[u]=dfn[u]=++cnt_dfn;
    for(auto x:g[u]){
        if(dfn[x]==0){
            tarjan(x,u);
            low[u]=min(low[u],low[x]);
            if(low[x]>dfn[u]){
                bridges.push_back(make_pair(u,x));
            }
        }else if(f!=x){
            low[u]=min(low[u],dfn[x]);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值