• 普及常见图论算法整理



    约定

    我是怎么存图的呢? 普通的邻接表。

    const int N = 1e5+15; // 点数 
    const int M = 1e6+15; // 边数 
    int ct,hd[N],nt[M<<1],vr[M<<1],vl[M<<1];
    void ad(int a,int b,int c) { // 加一条 e = a->b, w(e)=c 的有向边 e
    	vr[++ct]=b,vl[ct]=c;
    	nt[ct]=hd[a],hd[a]=ct;
    }
    

    一般连通无向图的信息、操作

    遍历

    遍历嘛……

    bool inq[N];
    inline void prev(int x) { inq[x]=true;  }
    inline void posv(int x) { inq[x]=false; }
    void dfs(int x) {
    	prev(x); //第一次访问时做点啥 
    	for(int i=hd[x];i;i=nt[i]) {
    		int y=vr[i];
    		if(!inq[y]) dfs(y);
    	}
    	posv(x); //回溯时也总要做点啥 
    	return;
    } 
    

    联通块

    加点东西就行了, 使用的时候调用

    for(int i=1;i<=n;++i)if(!col[i]) {
    		++tot; dfs(i);
    }
    

    即可

    int tot, col[N];
    bool inq[N];
    inline void prev(int x) { inq[x]=true; col[x]=tot;  }
    inline void posv(int x) { inq[x]=false; }
    void dfs(int x) {
    	prev(x);
    	for(int i=hd[x];i;i=nt[i]) {
    		int y=vr[i];
    		if(!inq[y]) dfs(y);
    	}
    	posv(x);
    	return;
    } 
    

    二分图判定

    没有奇环(环上节点数为奇数的环)就是二分图了, 具体可以对图二染色(对节点黑白染色,使得每个节点的相邻节点与其不同色), 看是否可以不矛盾地染完色。
    使用时调用

    col[1]=1, ans= dfs(1);
    

    即可

    int col[N];
    bool dfs(int x) {
    	for(int i=hd[x];i;i=nt[i]) {
    		int y=vr[i];
    		if(col[x] == col[y]) return false;
    		if(!col[y]) {
    			col[y]=3-col[x];
    			if(!dfs(y)) return false;
    		}
    	}
    	return true;
    } 
    

    割点割边

    不想盗图。
    (dfs) 过程中, 经过的边构成了一棵树,叫 dfs树。在dfs树上的边就叫 树边, 剩下的边叫 返祖边(其实叫 反向边 (back edge))。

    割点的判定方法
    树根 : 子节点数 (ge 2) 时, 树根为割点。
    非根 : 设此节点 (u)dfs树 上存在一子节点 (v) 使得以 (v) 为根的子树中的所有节点 (包括 (v)) 都没有连向 (u) 的祖先的返祖边, 那么 (u) 就是割点, 反之不是。
    (显然成立)

    实现就很经典了, 用到 (dfs) 序和 (low[]) 数组。
    使用时调用

    dfs(1,0);
    

    即可。

    //注意区分根和非根节点
    bool cut[N];
    int rt, tot,dfn[N],low[N];
    inline void prev(int x) {low[x]=dfn[x]= ++tot;  }
    void dfs(int x, int fa) {
    	prev(x);
    	int ch= 0;
    	for(int i=hd[x];i;i=nt[i]) {
    		int y= vr[i];
    		if(!dfn[y]) {
    			++ch;
    			dfs(y,x);
    			if(fa && low[y]>=dfn[x]) cut[x]= true;
    			low[x]= min(low[x],low[y]);
    		} else if(dfn[y]<dfn[x] && y!=fa) low[x]=min(low[x],dfn[y]);
    	}
    	if(fa==0 && ch>=2) cut[x]= true;
    }
    

    割边的判定方法
    同上, 在 (dfs) 树上判定割边。
    设一个节点 (u)dfs树 上存在一子节点 (v) 使得以 (v) 为根的子树中的所有节点 (包括 (v)) 都没有连向 (u) 及其祖先的返祖边, 那么 边((u,v)) 就是割边, 反之不是。
    (显然成立)

    实现时以上面的代码为基础加点东西就可以了, 但是要注意存无向边时是用两条有向边代替的, 当一条边 ((x,y)) 是割边时, 边 ((y,x)) 也是割边。


    点双边双

    概念明细

    点--双联通边--双联通 都属于无向连通图的 (性质)

    • 对于一个无向连通图, 如果任意两点 (u、v) 之间都存在至少两条点不重复(不包括 (u、v)) 的路径, 则说这个图是 点--双联通 的。(等价于图中无割点,或任意两条边都在同一个简单环中)
    • 类似的, 如果一个无向连通图的任意两点 (u、v) 之间都存在至少两条边不重复的路径, 则则说这个图是 边--双联通 的。(等价于图中无割边,或每条边都在至少一个简单环中)

    称无向连通图的 极大 点--双联通子图(极大的,即往其中加了点就不满足性质的节点数最多的) 为其 点双连通分量

    • 每条边恰好属于一个点双连通分量, 两个点双连通分量的可能会有公共点,如果有,则公共点必为割点; 割点必同时在至少两个点双连通分量里

    类似的,称无向连通图的 极大 边--双联通子图 为其 边双连通分量

    • 每个点恰属于一个边双联通分量, 除割边外, 每条边恰属于一个边双联通分量; 一条割边连接两个边双连通分量

    怎么求?
    点双不会。
    边双 就简单了, 首先求出所有割边, 然后断开所有割边((dfs) 时不走割边就行了), 剩下的每个联通块就是一个边双。

    我懒, 不想写代码了

    训练指南好啊。
    边双基础题


    有向图信息、操作

    拓扑排序

    拓扑排序 是作用于有向无环图的一种算法。
    拓扑排序就是按点的入数大小为顺序删点, 删去一个点时可能会有没被删的点的入度减小。
    (这个描述太 (SD) 我自己都忍俊不禁)

    强连通分量、缩点

    • 若对于一个有向图的任意两点 (u、v) 都可以相互到达, 则称这个有向图是强联通的。(具体有没有这个称呼我不清楚)
      有向图的 极大 强连通子图 似乎理所应当就叫 强连通分量 了。

    算法? Tarjan!!!
    怎么简洁准确地描述它呢……
    我没有足够的能力描述它。

    至于 缩点, 就是将有向图的每个强连通分量看成一个点, 建立一个新图。
    由于要存多个图, 所以如何写代码是我要思考的qwq。

    强连通分量+缩点板子

    #include<bits/stdc++.h>
    using namespace std;
    const int N = 1e4+15;
    const int M = 1e5+15;
    
    int n, m, val[N], val2[N];
    
    int ct, hd[N], nt[M<<1], vr[M<<1];
    void ad(int a,int b) {
    	vr[++ct]=b, nt[ct]=hd[a], hd[a]=ct;
    }
    
    int deg[N];
    int ct2, hd2[N], nt2[M<<1], vr2[M<<1];
    void ad2(int a,int b) {
    	vr2[++ct2]=b, nt2[ct2]=hd2[a], hd2[a]=ct2;
    }
    
    int sccno[N], scccnt;
    int S[N], tp;
    int dfn[N], low[N], clk;
    void dfs(int x) {
    	dfn[x]=low[x]= ++clk;
    	S[++tp]=x;
    	for(int i=hd[x];i;i=nt[i]) {
    		int y=vr[i];
    		if(!dfn[y]) {
    			dfs(y);
    			low[x]=min(low[x],low[y]);
    		} else if(!sccno[y]) low[x]=min(low[x],dfn[y]);
    	}
    	if(low[x]==dfn[x]) {
    		++scccnt;
    		while(1) {
    			int u=S[tp--];
    			sccno[u] = scccnt;
    			if(u==x) break;
    		}
    	}
    }
    
    int f[N];
    int q[N], h=1, t=0;
    void topo() {
    	for(int i=1;i<=scccnt;++i) if(!deg[i]) f[q[++t]=i] = val2[i];
    	while(h<=t) {
    		int x=q[h++];
    		for(int i=hd2[x];i;i=nt2[i]) {
    			int y=vr2[i];
    			f[y] = max(f[y], f[x]+val2[y]);
    			if(--deg[y] == 0) q[++t] = y;
    		}
    	}
    }
    
    int main()
    {
    	scanf("%d%d", &n, &m);
    	for(int i=1;i<=n;++i) scanf("%d", &val[i]);
    	for(int i=0,x,y;i<m;++i) {
    		scanf("%d%d",&x,&y); ad(x,y);
    	}
    	for(int i=1;i<=n;++i) if(!dfn[i]) dfs(i);
    	for(int x=1;x<=n;++x) {
    		val2[sccno[x]] += val[x];
    		for(int j=hd[x];j;j=nt[j]) {
    			int y=vr[j];
    			if(sccno[x] != sccno[y]) ad2(sccno[x],sccno[y]), ++deg[sccno[y]];
    		}
    	}
    	
    	topo();
    	int ans = 0;
    	for(int i=1;i<=scccnt;++i) ans=max(ans, f[i]);
    	cout << ans;
    	return 0;
    }
    

    简单树论

    直径

    • 一棵树的直径就是这棵树上最长的一条路径, 直径可能不唯一

    可以 树形DP 求, 也可以两次 dfs
    dfs 方法好像得出方案更容易。
    这里给出 dfs方法

    dfs 求树直径的两端点

    • 随便找一个点 (s), 随便选一个距离其最远的点 (u)
    • 随便选一个距离 (u) 最远的点 (v)
      那么路径 (u ightarrow v) 就是这棵数的直径

    正确性?
    如果 (u) 确实是某条直径上的端点, 那么 (v) 就一定是另一个端点。
    为什么 (u) 一定是某条直径的一个端点呢? 明明 (s) 是随便选的啊!

    可以这样想:假定直径的两个端点分别为 (u、v), 不管 (s) 在不在直径上, 只要存在任意一点 (t) 距离 (s) 最远(大于 (s)(u) 的距离 和 (s)(v) 的距离), 那么就可以推出
    路径 (u ightarrow v) 不是直径。

    这题不错哟

    重心

    • 一棵树 (T) 上某一个节点 (x) 的一个特征值 (w(x)) 定义为 : 删掉 (x) 后余下的各个联通块节点数的最大值。
    • (w(x)) 最小的那个 (x) 就是树 (T) 的重心。
    • 树的重心似乎是不唯一的

    求法的话, 按照定义求就好了, 这里有一份参考代码。

    int siz[N], cg=0, cgw=n; // cg:重心, cgw:重心的 w 函数值 
    void dfs(int x) {
    	siz[x] = 1;
    	int w = 0;
    	for(int i=hd[x];i;i=nt[i]) {
    		int y=vr[i];
    		if(siz[y]) continue;
    		dfs(y);
    		siz[x] += siz[y];
    		w = max(w, siz[y]);
    	}
    	w = max(w, n-siz[x]);
    	if(w < cgw) {
    		cgw = w;
    		cg = x;
    	}
    }
    
  • 相关阅读:
    PHP获取http头信息
    zend studio 改变背景颜色
    WebApp之Meta标签
    js的左右滑动触屏事件
    越狱的 iPhone、iPad 通过网站实现一键安装 ipa 格式的 APP 应用
    移动平台的meta标签神奇的功效
    HTML5 localStorage本地存储实际应用举例
    移动端开发小结
    openSUSE 安装源
    两种方法求Int最值
  • 原文地址:https://www.cnblogs.com/tztqwq/p/13019726.html
Copyright © 2020-2023  润新知