首页 > 编程语言 > 详细

图论算法——基本图论算法小结

时间:2019-08-20 22:05:27      阅读:121      评论:0      收藏:0      [点我收藏+]

·听了一天的浑浑噩噩

·这个老师近距离看有点精致可爱,然鹅他过于强

·老师经典语句:

“来我们看一道简单题”,然鹅,是蓝题

“来我们再来看一道题",然鹅,是紫题

tql!!!tjl!!!%%%%%

 

一、图的入门介绍

·什么是图?——G(graph)=(V(点),E(边))

把图进行赋值,所赋值即为权值——点权,边权.

·图的储存

edge:next,to.

   eg:x——>y

   next:下一个以x为开头的边在数组的位置

   to:y

   first:以x为开头的第一条边

储存方法:找到最后,在往前倒

板子:

struct edge {
	int next, to;
	edge() {}
	edge(int _next, int _to) : next(_next), to(_to) {}
} e[M];

void add_edge(int x, int y) {
	e[++tot] = edge(first[x], y);
	first[x] = tot;
	e[++tot] = edge(first[y], x);
	first[y] = tot;
}

int main() {
	for (int x = first[p]; x; x = e[x].next) { //find linkers of point p
		q = e[x].to;
	}
}

升级版板子(带stl):

#include <vector>

vector<int> to[N];

to[x].push_back(y);
for (int x = 0; x < to[p].size(); ++x) {
	q = to[p][x]
}  

·例题:

联合权值https://www.luogu.org/problem/P1351

以下为此题温馨提示(dl可自行忽略):

(x,y), dis[x][y]=2.
以s为中心对答案的贡献:
ab+bc+ca+cd+bd+cd
=[(a+b+c+d)²-(a²+b²+c²+d²)]/2.

技术分享图片

 ·相关补充内容:

1、团(完全图)

有n个点,点之间两两都有边,共有n(n-1)/2条边.

【例题:

给定3n个点,保证有一个大小为2n的团.
求任意一个大小为n的团.

——方法:

3n-2n=n.故最多有n个不在团中的点—故删除2n个包括n个不在团中的点即可。
每次找到一个没有连边的点对(x, y),把他们两个都删了.
删n次剩下的就是一个大小为n的团.】

2、图的退化—>树

图:n个点,m条边

树:n个点,n-1条边,连通

3、dfs序:

顾名思义,dfs序就是dfs的顺序,分两种。

第一种:处理子树问题.

      即为  1,2,5,6,3,7,8,12,13,4,9,10,11

第一种dfs序中每个子树可以看成dfs序中的一段连续区间.
子树问题 ? 序列问题

第二种:处理树链问题.

      即为  1,2,5,-5,6,-6,-2,3,7,-7,8,12,-12,13,-13,-8,-3,4,9,-9,10,-10,11,-11,-4,-1。

求[1,2,5,4,11]—合并[1,2,5,-5,6,-6,-2,3,7,-7,8,12,-12,13,-13,-8,-3,4,9,-9,10,-10,11]得到[1,4,11]

合并 [1,2,5],由此得到[1,2,5,4,11].

第二种dfs序中每个树链可以看成dfs序中的一段连续区间.
树链问题 ? 序列问题.

技术分享图片

4、取反图

·遍历所有头节点,方向取反.

·直接上板子:

技术分享图片

 


 

二、最短路

·源点s:起点,汇点T:终点.

·1、单源最短路:一个点为起点,所有点为终点.

 2、多源最短路:所有点为起点,所有点为终点.

·求最短路算法:

1、多源:Bellmen Ford (spfa), Floyd

2、单源:Dijkstra

 


 

三、flyod

·这是最简单的一个!!!!!!!!!!!

·多源最短路,所有点到所有点。

·动态规划.

·F[i][j][k]表示从i到j的最短路,其中经过的点只有1, 2, 3, …, k, i, j这些.
 F[i][j][k] = min(F[i][j][k – 1], F[i][k][k – 1] + F[k][j][k – 1]).
 显然k这一维可以省略.
 F[i][j] = min(F[i][j], F[i][k] + F[k][j]).
 k从1到n遍历一遍就好了.
 时间复杂度:O(N^3).

·初始值:没给的边赋值为∞

·板子:

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

 


 

 四、dijkstra

·单源最短路,一到多.

·数组dis[],dis[p]表示源点S到点i的最短路径长度.(暂时)
 集合T,p∈T表示源点S到点p的最短路径长度已经被求出来了.
·初始化:一开始只有S在T中,除了dis[S] = 0,别的dis = inf.
·贪心思想:每次找到不在T中的所有点中,dis最小的点p,把p丢进T,然后更新所有和p相邻的dis值.
 重复n - 1遍上述操作,就求出了n个所有点的最短路.

·考虑先后更新的两个点v1, v2.
由dijkstra的步骤知道,dis[v1] < dis[v2].
而S到v2的前一步,一定经过一个小于dis[v2]的点v.
我们知道这所有的v都已经在v2前已经求出来了,所以的确可以求出来正确的dis[v2].
证明也同时说明,dijkstra算法要求所有的边边权都是正的.
时间复杂度:O(n^2).
堆优化T ? O(nlogn).

·限制条件:所有边的边权为正.

·板子:

struct edges {
	int next, to, v;
	edges() {}
	edges(int _n, int _t, int _v) : next(_n), to(_t), v(_v) {}
} e[];

struct heap_node {
	int v, to;
	heap_node() {}
	heap_node(int _v, int _to) : v(_v), to(_to) {}
};
inline bool operator < (const heap_node &a, const heap_node &b) {
	return a.v > b.v; //这里实现的是小根堆,如果要大根堆,把 > 改成 < 即可
}

priority_queue <heap_node> h;

inline void add_to_heap(const int p) {
	for (int x = first[p]; x; x = e[x].next)
		if (dis[e[x].to] == -1)
			h.push(heap_node(e[x].v + dis[p], e[x].to));
}

void Dijkstra(int S) {
	memset(dis, -1, sizeof(dis));
	while (!h.empty()) h.pop();
	dis[S] = 0, add_to_heap(S);
	int p;
	while (!h.empty()) {
		if (dis[h.top().to] != -1) {
			h.pop();
			continue;
		}
		p = h.top().to;
		dis[p] = h.top().v;
		h.pop();
		add_to_heap(p);
	}
}

 


 

五、Bellman Ford(spfa)

·spfa = 队列优化bellman ford.
·初始化:一开始dis[S] = 0, 对于其他的p,dis[p] = inf.
维护一个队列,队列里面存是dis被更新的点.
·每次取出队首,用队首的dis信息更新和它相连的其他点的dis,然后把这些点不在队列里且dis值变小的重新入队.
队列为空表示没有要更新的点,算法结束.

·每次进队列的点都是dis值更新的点.
所以这个点的dis已经最小的话,就不会更新,也就是不会再进队了.
同时这个点的dis能达到最小值,否则它的最短路径上的前一个点的dis也不是最小值,以此类推,所有最小值路径上的dis都不是最小值,也即dis[S] > 0,这显然不对.

·理论复杂度O(NM).
·N是点数,M是边数.
·实际跑起来很快,但是会被特定结构的图卡到理论上界.
e.g. 奶牛题专卡spfa.
一般建议不写.

·SLF优化:

spfa有时候会被卡,这时候适当地添加SLF优化可以不被卡.
SLF优化在特定图下还是会被卡,不过被卡的几率很小.
SLF优化的地方就是每次更新dis进队时,判断一下是不是当前点的dis值比队头还小:如果是的话,直接把当前点插入队头.
只需要加一句if就可以了,很方便而且不容易被卡.

·板子:

纯spfa:

struct edge {
	int next, to, v;
}e[];

void spfa(int S) {
	int p, x, y, l, r;
	for (x = 1; x <= n; ++x)
		dis[x] = inf;
	q[0] = S, dis[S] = 0, v[S] = 1;
	for (l = r = 0; l != (r + 1) % N; ) {
		p = q[l], ++l %= N;
		for (x = first[p]; x; x = e[x].next)
			if (dis[p] + e[x].v < dis[(y = e[x].to)]) {
				dis[y] = dis[p] + e[x].v;
				if (!v[y]) {
					v[y] = 1;
					q[++r %= N] = y;
				}
			}
		v[p] = 0;
	}
} 

带slf优化的spfa:

struct edge {
	int next, to, v;
}e[];

int inc(int x) {
	x = x + 1;
	x = x % N;
	return x;
}

int dec(int x) {
	x = x - 1 + N;
	x = x % N;
	return x;
}

void spfa(int S) {
	int p, x, y, l, r;
	for (x = 1; x <= n; ++x)
		dis[x] = inf;
	q[0] = S, dis[S] = 0, v[S] = 1;
	for (l = r = 0; l != (r + 1) % N; ) {
		p = q[l], inc(l);
		for (x = first[p]; x; x = e[x].next)
			if (dis[p] + e[x].v < dis[(y = e[x].to)]) {
				dis[y] = dis[p] + e[x].v;
				if (!v[y]) {
					v[y] = 1;
					if (dis[y] < dis[q[l]]) q[dec(l)] = y; //SLF
					else q[inc(r)] = y;
				}
			}
		v[p] = 0;
	}
}   

·TIPS:求最短路可用bfs,不要用dfs(否则会爆时)

·相关补充内容:

出边:出去;入边:进入。

出度:往外指的边的条数;入度:指向它的边的条数。

 


 

六、差分约束系统 

 

·一组二元不等式.
·题面:

x[i] – x[j] ≤ k.

求x[i] – x[0]的最小值.

·对一个x[i] – x[j] ≤ k:

j指向i有一条长度为k的边.
求以0为起点的最短路.

·对照:

技术分享图片

·什么时候无解?

---有负环的时候.

eg:x[2]-x[1]<=-1

       x[1]-x[2]<=-1

相加,0<=-2,显然不成立,故无解.

·如果把小于号改成大于号,求最小值呢?
---最长路.

·如果有一部分是大于号并且求最大值呢?
---把大于号改成小于号并求最短路.

 


 

七、二分图匹配

·二分图:两部分:一部分点集为x、y,x、y中两两不相连.

·匹配:左边点与右边点连成一条边。

二分图匹配:能连多少边且每个点至多被连了一次。

·算法:匈牙利算法介于当前匹配情况去搜有没有更大的匹配,找到一个之前未匹配的点.

找到2个点,2/2=1,3—>4。

每次加新边,右边只能dfs一次.

 

贪心的思想.
每次找一条路径,是非匹配-匹配相互交替的.
如果找到一个2k长度的路径,说明有一个更大的匹配方式.
每次找路径都是暴力搜,所以每次寻找路径都要搜满m条边.
对每个点都要搜一次,所以总时间复杂度是O(NM).

技术分享图片

 

·板子:

int find(int x) {
	for (int i = 1; i <= n; ++i)
		if (a[x][i] == 1 && flag[i] == 0) {
			flag[i] = 1;
			if (pair[i] == 0 || find(pair[i])) {
				pair[i] = x;
				return 1;
			}
		}
	return 0;
}

int edmonds() {
	int cnt = 0;
	memset(pair, 0, sizeof(pair));
	for (int i = 1; i <= n; ++i) {
		memset(flag, 0, sizeof(flag));
		if (find(i)) ++cnt;
	}
	return cnt;
}  

 


 

八、Kruskal,最小生成树

·生成树:(对不起我也解释不好请自行意会)就是在原树上生成的一棵小树.

·最小生成树:边权最小的生成树.

·一般在无向图上做.

·最小生成树的性质:对于一条不在最小生成树上的边,它与最小生成树形成了一个环,那么这个环上这条边的权值是最大的之一.

做题基本要用到这个大佬的性质.

· 最小生成树不是唯一的,往往存在多个。

·算法:Kruskal

把所有边权从小到大排序,依次检查每一条边是否在最小生成树上.
两种case:
1. 边两边的点本来已经在同一棵树中了,那么会形成一个环.
2. 边两边的点不来不在一棵树中,那么会把两棵树变成一棵.
对于case1,显然环上其他的边权值都不大于它,也就是说它是环上权值最大的边之一,故它不应该在最小生成树上.
对于case2,直接把这条边加入最小生成树就可以了.

·怎么判断两个点在不在同一棵树里?

并查集!
一开始每个点都在一个独立的集合里.
每次合并两棵树 ? 集合并.
查询插入边有没有成环 ? 集合查询.

并查集作用:检查连通性。

两点不连通—>加边。

两点原来连通—>不加边。

把所有点都加一遍。

·最小生成树:从小往大加边。

 最大生成树:从大往小加边。 

·板子:

struct edges{
    int from, to, v;
    edges() {}
    edges(int a, int b, int c) : from(a), to(b), v(c) {}
}e[];
inline bool operator <(const edges &a, const edges &b){
	return a.v < b.v;  //如果是最大生成树,则把 < 改成 > 即可
}


inline void add_edge(int X, int Y, int Z){
	e[++tot] = edges(X, Y, Z);
}

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

int Kruskal(){
	sort(e + 1, e + tot + 1);
	int i;
	for (i = 1; i <= n; ++i)
		fa[i] = i;
	int fax, fay, cnt = n - 1, res = 0;
	for (i = 1; i <= tot; ++i){
		fax = find_fa(e[i].from), fay = find_fa(e[i].to);
		if (fax != fay){
			--cnt;
			fa[fax] = fay;
			res += e[i].v;
			if (!cnt) return res;
		}
	}
	return -1;
}

·相关补充内容:

次大生成树。

例题:Bzoj1977次小生成树Tree

-严格次小生成树:当前次小生成树边权和>最小生成树边权和.

改一条边。

-做法:最小生成树+LCA

先做出一个MST.
对每一条非树边边,加入MST后会形成一个环.
删除环上的除了这条非树边的最大值就好了.
但是权值可能相同,所以还要记录一下严格次大边是多少.

-板子:

#include <cstdio>
#include <algorithm>
 
using namespace std;
typedef long long ll;
const int N = 100001;
const int M = 300001;
struct data{
    int x, y, v;
    bool selected;
}a[M];
struct edge{
    int next, to ,v;
}e[N * 2];
struct tree_node{
    int dep, fa[17], d1[17], d2[17];
}tr[N];
inline bool operator < (const data a, const data b){
    return a.v < b.v;
}
 
int n, m, cnt, tot, del = 1e9;
int first[N], fa[N];
ll ans;
 
inline int read(){
    int x = 0, sgn = 1;
    char ch = getchar();
    while (ch < ‘0‘ || ch > ‘9‘){
        if (ch == ‘-‘) sgn = -1;
        ch = getchar();
    }
    while (ch >= ‘0‘ && ch <= ‘9‘){
        x = x * 10 + ch - ‘0‘;
        ch = getchar();
    }
    return sgn * x;
}
 
inline void add_edge(int x, int y, int z){
    e[++tot].next = first[x], first[x] = tot;
    e[tot].to = y, e[tot].v = z;
}
 
void add_Edges(int X, int Y, int Z){
    add_edge(X, Y, Z);
    add_edge(Y, X, Z);
}
 
int find_fa(int x){
    return x == fa[x] ? x : fa[x] = find_fa(fa[x]);
}
 
void dfs(int p){
    int i, x, y, FA;
    for (i = 1; i <= 16; ++i){
        if (tr[p].dep < (1 << i)) break;
        FA = tr[p].fa[i - 1];
        tr[p].fa[i] = tr[FA].fa[i - 1];
        tr[p].d1[i] = max(tr[p].d1[i - 1], tr[FA].d1[i - 1]);
        if (tr[p].d1[i - 1] == tr[FA].d1[i - 1])
            tr[p].d2[i] = max(tr[p].d2[i - 1], tr[FA].d2[i - 1]);
        else {
            tr[p].d2[i] = min(tr[p].d1[i - 1], tr[FA].d1[i - 1]);
            tr[p].d2[i] = max(tr[p].d2[i - 1], tr[p].d2[i]);
            tr[p].d2[i] = max(tr[p].d2[i], tr[FA].d2[i - 1]);
        }
    }
    for (x = first[p]; x; x = e[x].next)
        if ((y = e[x].to) != tr[p].fa[0]){
            tr[y].fa[0] = p, tr[y].d1[0] = e[x].v, tr[y].dep = tr[p].dep + 1;
            dfs(y);
        }
}
 
inline int lca(int x, int y){
    if (tr[x].dep < tr[y].dep) swap(x, y);
    int tmp = tr[x].dep - tr[y].dep, i;
    for (i = 0; i <= 16; ++i)
        if ((1 << i) & tmp) x = tr[x].fa[i];
    for (i = 16; i >= 0; --i)
        if (tr[x].fa[i] != tr[y].fa[i])
            x = tr[x].fa[i], y = tr[y].fa[i];
    return x == y ? x : tr[x].fa[0];
}
 
void calc(int x, int f, int v){
    int mx1 = 0, mx2 = 0, tmp = tr[x].dep - tr[f].dep, i;
    for (i = 0; i <= 16; ++i)
        if ((1 << i) & tmp){
            if (tr[x].d1[i] > mx1)
                mx2 = mx1, mx1 = tr[x].d1[i];
            mx2 = max(mx2, tr[x].d2[i]);
            x = tr[x].fa[i];
        }
    del = min(del, mx1 == v ? v - mx2 : v - mx1);
}
 
void work(int t, int v){
    int x = a[t].x, y = a[t].y, f = lca(x, y);
    calc(x, f, v);
    calc(y, f, v);
}
 
int main(){
    n = read(), m = read();
    int i, f1, f2, TOT = 0;
    for (i = 1; i <= m; ++i)
        a[i].x = read(), a[i].y = read(), a[i].v = read();
    for (i = 1; i <= n; ++i)
        fa[i] = i;
    sort(a + 1, a + m + 1);
    for (i = 1; i <= m; ++i)
        if ((f1 = find_fa(a[i].x)) != (f2 = find_fa(a[i].y))){
            fa[f1] = f2;
            ans += a[i].v;
            a[i].selected = 1;
            add_Edges(a[i].x, a[i].y, a[i].v);
            ++TOT;
            if (TOT == n - 1) break;
        }
    dfs(1);
    for (i = 1; i <= m; ++i)
        if (!a[i].selected)
            work(i, a[i].v);
    printf("%lld\n", ans + del);
    return 0;
}

 Ps/ h[][]记录严格次大边,G[][]记录最大边

技术分享图片

在处理时,如上述代码,a、b边可分开处理。

图论算法——基本图论算法小结

原文:https://www.cnblogs.com/konglingyi/p/11384786.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!