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 层点的边来表示一次操作。
最短路
堆优化的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;
}