普及常见图论算法整理
Posted tztqwq
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了普及常见图论算法整理相关的知识,希望对你有一定的参考价值。
约定
我是怎么存图的呢? 普通的邻接表。
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;
}
}
以上是关于普及常见图论算法整理的主要内容,如果未能解决你的问题,请参考以下文章