Graph 图论

Graph 图论

图的存储方式

vector存图
const int maxn = 2e5 + 7;

vector<int > g[maxn];
vector<vector<int> > g(maxn);
vector<vector<pair<int, int> > > g(maxn);
链式前向星
  • 慎用,可能会很慢;
  • 可以在网络流相关的题使用,快速得到与本条边反向的边(i ^ 1);
struct LSQXX {
	struct Edge {
		int ne, to, fl;
		ll w;

		Edge(int ne_ = 0, int to_ = 0, ll w_ = 0, int fl_ = 0) : ne(ne_), to(to_), w(w_), fl(fl_) {

		}

		bool operator<(const Edge& a) const {
			return w < a.w;
		};
	};
	int n, ptr;
	vector<int> head;
	vector<Edge> e;

	LSQXX (int n_ = 0) : n(n_), head(n + 1, -1){ 
		ptr = -1;
	} 

	void add(int fr, int to, ll w, int fl = 0) {
		e[++ptr] = Edge(head[fr], to, w, fl);
		head[fr] = ptr;
	}

	void Sort() {
		sort(e.begin(), e.begin() + ptr);
	}
	
};
存边
const int maxn = 5e6 + 7;

struct EDGE {
	struct Edge {
		int fr, to, fl;
		ll w;

		Edge(int fr_ = 0, int to_ = 0, ll w_ = 0, int fl_ = 0) : fr(fr_), to(to_), w(w_), fl(fl_) {

		}

		bool operator<(const Edge& a) const {
			return w < a.w;
		};
	};
	int n, ptr;
	vector<Edge> e;

	EDGE (int n_ = 0) : n(n_), e(maxn) { 
		ptr = 0;
	} 

	void add(int fr, int to, ll w, int fl = 0) {
		e[++ptr] = Edge(fr, to, w, fl);
	}
	
	void Sort() {
		sort(e.begin() + 1, e.begin() + 1 + ptr);
	}
	
};

图的创建方式

	int n, m;
	cin >> n >> m;
	vector<vector<int> > g(n + 1);
	for(int i = 1;i <= m;i++) {
		int u, v;
		cin >> u >> v;
		g[u].push_back(v);
        g[v].push_back(u);
	}
	function<void(int, int)> dfs = [&](int x, int fa) {
		for(int to : g[x]) {
			if(to == fa) continue;
			dfs(to, x);
		}
	};

分层图
适用场景
  • 一些图论题,比如最短路、网络流等,题目对边的权值提供可选的操作,比如可以将一定数量的边权减半,在此基础上求解最优解。
算法思路

根据是否进行题目提供的操作以及操作次数的不同,会产生非常多的情况,如果考虑何时使用操作,情况更是多。如果将在图上求解最短路看成是在二维平面上进行的,引入进行操作的次数 k 做为第三维,那么这个三维空间就理应可以包含所有的情况,便可以在这个三维空间上解决问题。每进行一次操作(k+1),除了操作的边,其他边没有任何变化,在 k=0,1,2,…,时图都是一样的,那么就将图复制成 k+1 份,第 i 层图代表进行了 i 次操作后的图。每相邻两层图之间的联系,应该决定了一次操作是发生在哪条边上(何时进行操作)。根据操作的特点(对边权的修改)可以 i 层点到 i+1 层点的边来表示一次操作。

img

最短路

堆优化的Dijkstra

const int inf = 0x3f3f3f3f;
const int maxn = 1e5 + 7;
struct node {
	int to, ne, co;
}edge[maxn << 1];
int tot = 0, n, m;
vector<int> head(maxn, -1);
void add(int fr, int to, int co) {
	edge[++tot] = {to, head[fr], co};
	head[fr] = tot;
}
int Dijkstra(int st, int ed) {   
	//多组记得初始化
	priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > q;
	q.push(make_pair(0, st));
	vector<ll> dis(n + 1, inf);
	dis[st] = 0;
	while(q.size()) {
		pair<int, int> now = q.top();
		q.pop();
		if(now.first > dis[now.second]) continue;
		for(int i = head[now.second];i != -1; i = edge[i].ne) {
			int to = edge[i].to;
			int co = edge[i].co;
			if(dis[to] > dis[now.second] + co) {
				dis[to] = dis[now.second] + co;
				q.push(make_pair(dis[to], to));
			}
		}
	}
	return dis[ed];
}

SPFA

SPFA(Shortest Path Faster Algorithm)算法是求单源最短路径的一种算法,它是Bellman-ford的队列优化,它是一种十分高效的最短路算法。

很多时候,给定的图存在负权边,这时类似Dijkstra等算法便没有了用武之地,而Bellman-Ford算法的复杂度又过高,SPFA算法便派上用场了。SPFA的复杂度大约是O(kE),k是每个点的平均进队次数(一般的,k是一个常数,在稀疏图中小于2)。

但是,SPFA算法稳定性较差,在稠密图中SPFA算法时间复杂度会退化。

实现方法:建立一个队列,初始时队列里只有起始点,标记起点的vis为1,在建立一个dis数组记录起始点到所有点的最短路径(该表格的初始值要赋为极大值,该点到他本身的路径赋为0)。然后执行松弛操作,用队列里有的点去刷新起始点到所有点的最短路,如果刷新成功且被刷新点不在队列中则把该点加入到队列最后。重复执行直到队列为空。

此外,SPFA算法还可以判断图中是否有负权环,即一个点入队次数超过N。

Floyd

求最短路

	vector<vector<ll> > a(n + 1, vector<ll>(n + 1, 0x3f3f3f3f3f3f3f));
	vector<vector<ll> > dp(n + 1, vector<ll>(n + 1, 0x3f3f3f3f3f3f3f));
	
	for(int i = 1;i <= m;i++) {
		int u, v, w;
		cin >> u >> v >> w;
		a[u][v] = w;
		a[v][u] = w;
	}

	dp = a;

	ll ans = 0x3f3f3f3f3f3f3f;//最小环路值
	for(int k = 1;k <= n;k++) {
        
		for(int i = 1;i < k;i++) {
			for(int j = i + 1;j < k;j++) {
				ans = min(ans, a[i][j] + dp[j][k] + dp[k][i]);  //更新最小环路
			}
		}
        
		for(int i = 1;i <= n;i++) {
			for(int j = 1;j <= n;j++) {
				a[i][j] = min(a[i][j], a[i][k] + a[k][j]);
			}
		}
	}
}

LCA

倍增Lca

const int maxn = 1e5 + 4;
struct Edge {
	int to;
	int ne;
}edge[maxn << 1];
int ptr = 0;
vector<int > head(maxn, -1), dep(maxn, 0);
vector<vector<int> > st(maxn, vector<int>(25, 0));
int n, m;
void init() {
	ptr = 0;
	head = vector<int>(maxn, -1);
	st = vector<vector<int> >(maxn, vector<int>(25, 0));
	dep = vector<int>(maxn, 0);
}
void add(int st, int end) {
	ptr++;
	edge[ptr].to = end;
	edge[ptr].ne = head[st];
	head[st] = ptr;
	ptr++;
	edge[ptr].to = st;
	edge[ptr].ne = head[end];
	head[end] = ptr;
}
void dfs(int now, int fa) {
	dep[now] = dep[fa] + 1;
	st[now][0] = fa;
	for(int i = 1;(1 << i) <= dep[now];i++) {
		st[now][i] = st[st[now][i - 1]][i - 1];
	}
	for(int i = head[now]; i != -1;i = edge[i].ne) {
		int nex = edge[i].to;
		if(nex == fa) continue;
		st[nex][0] = now;
		dfs(nex, now);
	}
}
int lca(int x, int y) {
	if(dep[x] < dep[y]) swap(x, y);
	for(int i = 14;i >= 0;i--){
		if(dep[st[x][i]] >= dep[y]) {
			x = st[x][i];
		}
		if(x == y) return x;
	}
	for(int i = 14;i >= 0;i--) {
		if(st[x][i] != st[y][i]) {
			x = st[x][i];
			y = st[y][i];
		}
	}
	return x == y?x : st[x][0];
}

树剖Lca

const int maxn = 1e6 + 7;

struct segTree {
	//@author  Zjkai
	int l, r;
	ll val, f;
}tr[maxn << 2];

struct Edge {
	int ne, to, w;
}edge[maxn];

vector<int> head(maxn, -1);
vector<int> fa(maxn, 0), dep(maxn, 0), son(maxn, 0), size(maxn, 0); //dfs_1
// 每个点的父亲//深度数组//重儿子数组//子树大小数组
vector<int> L(maxn, 0), invL(maxn, 0), top(maxn, 0);                //dfs_2
// DFN // DFN中对应的点//重链顶
vector<int> pi(maxn, 0);
//点权
int tot = 0, n, m, r, p, Time = 0;

void add(int fr, int to, int cost) {
	++tot;
	edge[tot].ne = head[fr];
	edge[tot].to = to;
	edge[tot].w = cost;
	head[fr] = tot;
}

void dfs_1(int now) {//预处理1
	size[now] = 1;
	dep[now] = dep[fa[now]] + 1;
	for(int i = head[now];i != -1;i = edge[i].ne) {
		int to = edge[i].to;
		if(to != fa[now]) {
			fa[to] = now;
			dfs_1(to);
			size[now] += size[to];
			if(size[to] > size[son[now]]) son[now] = to;
		}
	}
	return ;
}

void dfs_2(int now, int tp) {// 预处理2
	L[now] = ++Time;
	invL[Time] = now;
	top[now] = tp;
	if(son[now]) dfs_2(son[now], tp);

	for(int i = head[now];i != -1;i = edge[i].ne) {
		int to = edge[i].to;
		if(to != fa[now] && to != son[now]) {
			dfs_2(to, to);   //轻链的顶就是自己!
		}
	}
	return ;
}

int lca(int x, int y) {
	while(top[x] != top[y]) {
		if(dep[top[x]] >= dep[top[y]]) x = fa[top[x]];
		else y = fa[top[y]];
	}
	return dep[x] < dep[y]? x : y;
}

Tarjan

const int maxn = 1e5 + 7;

vector<vector<int> > g(maxn), g_(maxn);
vector<int> dfn(maxn), low(maxn), scc(maxn), size(maxn << 1);
stack<int> st;

int Time, cnt;

void tarjan(int x) {
	low[x] = dfn[x] = ++Time;
	st.push(x);
	vis[x] = 1;
	for(int to : g[x]) {
		if(dfn[to] == 0) {
			tarjan(to);
			low[x] = min(low[x], low[to]);
		}
		else if(vis[to]) {
			low[x] = min(low[x], dfn[to]);
		}
	}
	if(low[x] == dfn[x]) {
		++cnt;
		while(1) {
			int tp = st.top();
			vis[tp] = 0;
			scc[tp] = cnt;
			size[cnt]++;
			st.pop();
			if(tp == x) break;
		}
	}
}

虚树

#include <bits/stdc++.h>

using namespace std;
using ll = long long;

const int maxn = 250007;
const int inf = 1e9;
vector<int> RG[maxn],VG[maxn];
int U[maxn],V[maxn],C[maxn];
int dfn[maxn],deep[maxn];ll me[maxn];int fa[maxn][20];
int stk[maxn],top;
int n,m,idx;
void dfs(int x){
	dfn[x]= ++idx;
	deep[x] = deep[fa[x][0]] + 1;
	for(int to : RG[x]){
		if(to == fa[x][0]) continue;
		fa[to][0] = x;
		dfs(to);
	}
}

int LCA(int u,int v){
	if(deep[u] < deep[v]) swap(u,v);
	int delta = deep[u] - deep[v];
	for(int i = 19;i >= 0;--i){
		if((delta >> i) & 1) u = fa[u][i];
	}
	for(int i = 19;i >= 0;--i){
		if(fa[u][i] != fa[v][i]) u = fa[u][i],v = fa[v][i];
	}
	if(u == v) return u;
	return fa[u][0];
}

bool comp(int a,int b){
	return dfn[a] < dfn[b];
}

void insert(int u){
	if(top == 1) {stk[++top] = u;return;}
	int lca = LCA(u,stk[top]);
	if(lca == stk[top]) {stk[++top] = u;return ;}
	while(top > 1 && dfn[lca] <= dfn[stk[top-1]]){
		VG[stk[top-1]].push_back(stk[top]);
		VG[stk[top]].push_back(stk[top-1]);
		--top;
	}
	if(lca != stk[top]) {
		VG[lca].push_back(stk[top]);
		VG[stk[top]].push_back(lca);
		stk[top] = lca;
	} 
	stk[++top] = u;
}

int idq[maxn],mark[maxn];

void DP(int x, int fa){
	printf("%d ", x);
	for(int to : VG[x]) {
		if(to == fa) continue;
		DP(to, x);
	}
}

int main(){
	ios::sync_with_stdio(false);
	cin >> n;
	int sz;
	cin >> sz;
	for(int i = 1;i < n;++i){
		cin >> U[i] >> V[i];
		RG[U[i]].push_back(V[i]);
		RG[V[i]].push_back(U[i]);
	}
	dfs(1);
	for(int t = 1;t <= 19;++t) for(int i = 1;i <= n;++i){
		fa[i][t] = fa[fa[i][t-1]][t-1];
	}
	for(int j = 0;j < sz;++j){
		cin >> idq[j];
		mark[idq[j]] = 1;
	}
	sort(idq,idq+sz,comp);
	top = 0;
	stk[++top] = 1;
	for(int j = 0;j < sz;++j) insert(idq[j]);
	while(top > 0) {
		VG[stk[top-1]].push_back(stk[top]);
		top--;
	}
	DP(idq[0], 0);
	for(int j = 0;j < sz;++j) VG[idq[j]].clear(),mark[idq[j]] = 0;
	VG[0].clear();


	return 0;
}

最小生成树

Kruskal

const int maxn = 5e5 + 7;
vector<int> fa(maxn, 0);
int n, m;
struct Edge {
	int fr, to, w;
	bool operator<(const Edge& b) const {
		return w < b.w;
	}
}edge[maxn];
int find(int x) {
	return x == fa[x]? x : fa[x] = find(fa[x]);
}
void init() {   // 一定要调用
	cin >> n >> m;
	for(int i = 1;i <= n;i++) fa[i] = i;
	for(int i = 1;i <= m;i++) cin >> edge[i].fr >> edge[i].to >> edge[i].w;
	sort(edge + 1, edge + 1 + m);
}
void work(){  //core!
	init();
	ll cnt = 0, mst = 0;
	for(int i = 1;i <= m;i++) {
		int fx = find(edge[i].fr);
		int fy = find(edge[i].to);
		if(fx != fy) {
			cnt++;
			mst += edge[i].w;
			fa[fx] = fy;
		}
		if(cnt == n - 1) break;
	}
    //不联通图没有最小生成树,注意
	cout << mst << '\n';
}

Prim

const int maxn = 5e3 + 5;

vector<vector<int> > g(maxn, vector<int>(maxn, 0x3f3f3f3f));

int Prim(int n) {
	vector<int> dis(n + 1, 0x3f3f3f3f);
	vector<int> vis(n + 1, 0);
	int mst = 0;
	for(int i = 1;i <= n;i++) dis[i] = g[1][i];
	vis[1] = 1;
	for(int i = 2;i <= n;i++) {
		int id = -1, minEdge = 0x3f3f3f3f;
		for(int j = 1;j <= n;j++) {
			if(!vis[j] && minEdge > dis[j]) {
				minEdge = dis[j];
				id = j;
			}
		}
		vis[id] = 1;
		mst += minEdge;
		for(int j = 1;j <= n;j++) {
			if(vis[j] == 0 && dis[j] > g[id][j]) {
				dis[j] = g[id][j];
			}
		}
	}
	return mst;
}

次小生成树

最小树形图

差分约束系统

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值