图论模板

Posted streamazure

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了图论模板相关的知识,希望对你有一定的参考价值。

图论模板

最短路

Dijkstra

struct HeapNode {
    int u; LL d;
    bool operator < (const HeapNode& rhs) const {
        return d > rhs.d;
    }
};
bool done[maxn];
LL d[maxn];
void dijkstra(int s) {
    pq<HeapNode>Q;
    for (int i = 0; i <= n; i++) d[i] = INF;
    d[s] = 0;
    memset(done, 0, sizeof(done));
    HeapNode t; t.u = s; t.d = 0;
    Q.push(t);
    while (!Q.empty()) {
        HeapNode x = Q.top(); Q.pop();
        int u = x.u;
        if (done[u]) continue;
        for (int i = head[u]; i; i = e[i].next) {
            int v = d[i].to
            if (d[u] + e[i].w < d[v]) {
                d[v] = d[u] + e[i].w;
                t.u = v; t.d = d[v];
                Q.push(t);
            }
        }
        done[u] = true;
    }
}

SPFA

int cnt[maxn];
bool inq[maxn];
LL d[maxn];
bool spfa(int s) {
    queue<int>Q;
    memset(inq, 0, sizeof(inq));
    memset(cnt, 0, sizeof(cnt));
    for (int i = 0; i < n; i++) d[i] = INF;
    d[s] = 0;
    Q.push(s); inq[s] = true;
    while (!Q.empty()) {
        int u = Q.front(); Q.pop(); inq[u] = false;
        for (int i = head[u]; i; i = e[i].next) {
            int v = e[i].to;
            if (d[u]<INF && d[u] + e[i].w < d[v]) {
                d[v] = d[u] + e[i].w;
                if (!inq[v]) {
                    Q.push(v); inq[v] = true;
                    if (++cnt[v] > n) return false;
                    //如果某个点迭代了超过n次,说明存在可以无限缩短的最短路,即负环
                }
            }
        }
    }
    return true;
}

Floyd

LL d[maxn * 4][maxn * 4];
for (int i = 0;i < maxn ; i++) for (int j = 0;j < maxn ; j++) d[i][j] = (i == j) ? 0 : INF; 
for (int k = 0; k < n; k++)  
	for (int i = 0; i < n; i++)    
		for (int j = 0; j < n; j++)      
            d[i][j] = min(d[i][j], d[i][k] + d[k][j]);

Johnson

  1. 创建超级源点0,并以0为起点向图中各结点建边,边权均为0.
  2. 以0为起点进行SPFA,求出节点0到各节点(i)的最短路,记为(h[i])。由于SPFA的自身特点,这一步可以同时判断负环。
  3. 更新图上原来所有边的边权(w_i)w[i]+=h[u]-h[v],其中(u,v)是边(i)的起点和终点。
  4. 枚举图上所有点为起点进行Dijskra,求出图上任意两点(u,v)间的最短路(d[u][v])
  5. 更新(d[u][v]),即减去之前加上的边权,d[u][v]-=h[u]-h[v]
  6. 最后所得的(d[u][v])即最终答案。

差分约束

不等式标准化

对于不等式(v-u > c),转化为(v-u≥c+1)

对于不等式(v-u=c),转化为(v-u≥c)(v-u≤c)

求差的最大值,不等式全取(≥),反之取(≤)

建边

(1)求差的最大值,对应最短路。

  • 对于不等式(v-u ≤ c),建边addw(u, v, c)

(2)求差的最小值,对应最长路。

  • 对于不等式(v-u≥c),建边addw(u, v, c)

解的存在性

  • 无解:存在负权环
  • 任意解:起点终点不连通

最小环判定

void init(){
	memset(a,10,sizeof a);
	int x,y,z;
	for (int i=1;i<=m;i++){
		cin >> x >> y >> z;
		a[x][y] = a[y][x] = min(a[x][y],z);
	}
	memcpy(d,a,sizeof a);
}
 
void work(){
	LL ans = INF;
    //若有解,则ans为最小环的长度
	for (int k = 1;k <= n;k++){
		for (int i = 1;i < k;i++)
			for (int j = 1;j < i;j++)
				ans = min(ans, a[i][k] + a[k][j] + d[i][j]);
		for (int i = 1;i <= n;i++)
			for (int j = 1;j <= n;j++)	
				f[i][j] = min(f[i][j], f[i][k] + f[k][j]);
	}
}

二分图

二分图判定

bool dfs(int u, int c) {
	col[u] = c;
	for (int i = head[u]; i; i = e[i].next) {
		int v = e[i].to;
		if (col[v] == col[u]) return false;
		if (col[v] == -1 && !dfs(v, !col[u])) return false;
	}
	return true;
}

二分图最大匹配/最小点覆盖

bool vis[maxn];
int res[maxn];
//vis[i]记录节点i在试图改变匹配对象时成功与否
//res[i]记录节点i的匹配对象

bool match(int x) {
	//注意,参数x都是左部点
	for (int i = head[x]; i; i = e[i].next) {
		int y = e[i].to;
		if (!vis[y]) {
			vis[y] = true;
			if (!res[y] || match(res[y])) {
				res[x] = y;
				res[y] = x;
				//这里默认左部点和右部点的编号没有重复的
				return true;
			}
		}
	}
	return false;
}

int main() {
  ...
	int ans = 0;
	for (int i = 1; i <= p; i++) {
		memset(vis, false, sizeof(vis));
		//对于枚举的每一个左部点,右部点的状态都是还没尝试过
		if (match(i)) ans++;
	}
  ...
}

二分图最优匹配

bool vis[maxn];
int res[maxn];
int gap;
//记录能使节点配对成功的最小改变量
bool match(int x) {
	vis[x] = true;
   //别漏了这一步
	for (int i = head[x]; i; i = e[i].next) {
		int y = e[i].to;
		if (!vis[y]) {
			int tmp = val[x] + val[y] - e[i].dis;
			if (tmp == 0) {
				vis[y] = true;
				if (!res[y] || match(res[y])) {
					res[x] = y;
					res[y] = x;
					return true;
				}
			}
			else if (tmp > 0) {
				gap = min(gap, tmp);
			}
		}
	}
	return false;
}

void km() {
	for (int i = 1; i <= n; i++) {
		while (1) {
			gap = INF;
			memset(vis, false, sizeof(vis));
			if (match(i)) break;
			//找不到符合要求的边,降低期望,重新尝试匹配
			for (int i = 1; i <= p; i++) {
				if (vis[i]) val_x[i] -= gap;      		//左部点降低期望
			}
			for (int i = 1; i <= q; i++) {
				if (vis[i]) val_y[i] += gap;      		//右部点提高期望
			}
		}
	}
}

强连通分量

求强连通分量

int dfs_clock, scc_cnt;
int dfn[maxn], low[maxn], sccno[maxn];

void dfs(int u) {
    dfn[u] = low[u] = ++dfs_clock;
    //给节点u打上时间戳
    s.push(u);
    for (int i = head[u]; i; i = e[i].next) {
        int v = e[i].to;
        if (!dfn[v]) {
            //节点v还没被搜索到
            dfs(v);
            low[u] = min(low[u], low[v]);
            //维护祖先中最小的时间戳
        }
        else if (!sccno[v]) {
            low[u] = min(low[u], dfn[v]);
            //节点v已经被搜索过了,但还不属于某一个SCC
        }
    }
    //对节点u的所有后代节点完成搜索之后
    //开始判断节点u是不是这个强连通分量中第一个出现的节点
    if (low[u] == dfn[u]) {
        scc_cnt++;
        //SCC数量+1
        while (1) {
            int x = s.top(); s.pop();
            sccno[x] = scc_cnt;
            //给分量中的所有节点记录所在SCC的编号
            if (x == u) break;
            //访问完u之后,就完成了对这个SCC所有节点的访问,跳出
        }
    }
}

void find_scc(int n) {
    dfs_clock = scc_cnt = 0;
    mem(sccno, 0);
    mem(dfn, 0);
    for (int i = 1; i <= n; i++) {
        if (!dfn[i]) dfs(i);
    }
}

2-SAT问题

设点(i)为选情况1,点(i+n)为选情况2。根据题意,假定好所有“若A选……则B必须选……”的情况,连边。

如条件“A成立或B成立”,则假定情况“若A不成立,则B必须成立”和“若B不成立,则A必须成立”,连边如下

add(a+n, b)
add(b+n, a)

然后跑Tarjan。如出现sccno[i] == sccno[i+n],则无论如何都无法满足题意。

若需要输出字典序最小的一组解,则设点(i)为假,点(i+n)为真,则输出

for (int i = 1; i <= n; i++) {
	if (sccno[i] > sccno[i + n]) cout << "1 ";
	else cout << "0 ";
}

双连通分量

求双连通分量

stack<int> s;
int dfs(int u, int fa) {
    low[u] = dfn[u] = ++dfs_clock;
    int child = 0;
    for (int i = head[u]; i; i = e[i].next) {
        int v = e[i].to;
        if (!dfn[v]) {
            s.push(e[i]);
            child++;
            low[v] = dfs(v, u);
            low[u] = min(low[u], low[v]);
            if (low[v] >= dfn[u]) {
                iscut[u] = true;
                bcc_cnt++; bcc[bcc_cnt].clear();

                for (;;) {
                    Edge x = s.top(); s.pop();
                    if (bccno[x.from] != bcc_cnt) {
                        bcc[bcc_cnt].push_back(x.from);
                        bccno[x.from] = bcc_cnt;
                    }
                    if (bccno[x.to] != bcc_cnt) {
                        bcc[bcc_cnt].push_back(x.to);
                        bccno[x.to] = bcc_cnt;
                    }
                    if (x.from == u && x.to == v) break;
                }
            }
        }
        else if (dfn[v] < dfn[u] && v != fa) {
            s.push(e[i]);
            low[u] = min(low[u], dfn[v]);
        }
    }
    if (fa < 0 && child == 1) iscut[u] = false;
    return low[u];
}

void find_bcc() {
    dfs_clock = bcc_cnt = 0;
    for (int i = 1; i <= n; i++) {
        if (!dfn[i]) dfs(i, -1);
    }
}

求割点

int dfs(int u, int fa) {
    dfn[u] = low[u] = ++dfs_clock;
    int child = 0;
    for (int i = head[u]; i; i = e[i].next) {
        int v = e[i].to;
        if (!dfn[v]) {
            child++;
            low[v] = dfs(v, u);
            low[u] = min(low[v], low[u]);
            if (low[v] >= dfn[u]) {
                iscut[u]++;
            }
        }
        else if (dfn[v] < dfn[u] && fa != v) {
            low[u] = min(low[u], dfn[v]);
        }
    }
    if (fa < 0 && child == 1) iscut[u] = 0;
    return low[u];
}
int main(){
	...
	for (int i = 1; i <= n; i++) {
		if (!dfn[i]) dfs(i, -1);
	}
    ...
}

以上是关于图论模板的主要内容,如果未能解决你的问题,请参考以下文章

模板 - 图论 - 图的存储和遍历

VSCode自定义代码片段1——vue主模板

VSCode自定义代码片段2——.vue文件的模板

VSCode自定义代码片段(vue主模板)

图论模板

图论模板