图论部分知识总结

存图方法

全文使用的都是一种比较新型的存图方式——链式前向星。这种方式优势和劣势都不明显,属于是比较中庸的一种方式。正因为很中庸,所以可以闭着眼用,一般不会出问题,我很喜欢。

struct node {
	int to, next, w;
}edge[maxn << 2]; 
int head[maxn], cnt;

其中 e d g e [ i ] . t o edge[i].to edge[i].to表示第 i i i条边的终点, e d g e [ i ] . n e x t edge[i].next edge[i].next表示与第i条边同起点的下一条边的存储位置, e d g e [ i ] . w edge[i].w edge[i].w为边权值。

另外还有一个数组 h e a d [ ] head[] head[],它是用来储存以 i i i为起点的第一条边存储的位置,实际上这里的第一条边存储的位置其实在以 i i i为起点的所有边的最后输入的那条边的编号。

边的添加

void add(int u, int v, int w) { 
	edge[++cnt].to = v; 
	edge[cnt].w = w; 
	edge[cnt].next = head[u]; 
	head[u] = cnt;
}

如果是无向图的话记得要加两次边,还要加一次 a d d ( v ,   u ,   w ) add(v,\ u,\ w) add(v, u, w)

图的遍历

bool vis[maxn];
void dfs(int x) {
	vis[x] = true;
	for(int i = x; i; i = edge[i].next) {
		if(vis[edge[i].to]) continue;
		dfs(edge[i].to);
	}
}

最短路

多源最短路

Floyed

#include <iostream>
using namespace std;
#define maxn 305

int plat[maxn][maxn];
int tmpa, tmpb, tmpc, n, m, q;
void floyd() {
    for(register int k = 1; k <= n; ++k)
        for(register int i = 1; i <= n; ++i)
            for(register int j = 1; j <= n; ++j)
                if(i != j && i != k && j != k)
                    plat[i][j] = std::min(plat[i][j], plat[i][k] + plat[k][j]);
    }
int main() {
    cin >> n >> m;
    for(register int i = 1; i <= m; ++i) {
        cin >> tmpa >> tmpb >> tmpc;
        plat[tmpa][tmpb] = tmpc;
        plat[tmpb][tmpa] = tmpc;
    }
    floyd();
    cin >> q;
    for(register int i = 1; i <= n; ++i) {
        cin >> tmpa >> tmpb;
        cout << plat[tmpa][tmpb] << endl;
    }
}

单源最短路

堆优化的Dijkstra

在有负权边的时候不能用。当然你把所有的边加成正的也不是不行。

#include <iostream> 
#include <cstdio> 
#include <queue> 
using namespace std;
const int maxn = 100005;
struct node {
	int to, next, w;
}edge[maxn << 2]; 
struct node1 {
	int u, d;
	bool operator < (const node1 &a)const {
		return d > a.d; 
	}
};
int head[maxn], cnt, dist[maxn]; 
int n, m, s;
void add(int u, int v, int w) { 
	edge[++cnt].to = v; 
	edge[cnt].w = w; 
	edge[cnt].next = head[u]; 
	head[u] = cnt;
}
void Dijkstra(int x) {
	for(int i = 1; i <= n; i++) dist[i] = 2147483647; dist[x] = 0;
	priority_queue<node1> Q;
	Q.push((node1){x, 0});
	while(!Q.empty()) {
		node1 a = Q.top();
		Q.pop();
		int uu = a.u;
		int dd = a.d;
		if(dd != dist[uu]) continue;
		for(int i = head[uu]; i; i = edge[i].next) {
			int vv = edge[i].to;
			int ww = edge[i].w; 
			if(dist[vv] > dist[uu] + ww) {
                dist[vv] = dist[uu] + ww;
                Q.push((node1){vv, dist[vv]});
            }
		} 
	}
}
int main() {
	cin >> n >> m >> s;
	for(int i = 1; i <= m; i++) { 
		int tmpa, tmpb, tmpc;
		cin >> tmpa >> tmpb >> tmpc; 
		add(tmpa, tmpb, tmpc);
	}
	Dijkstra(s);
	for(int i = 1; i <= n; i++) cout << dist[i] << " "; 
	cout << endl;
	return 0;
}

利用优先队列可以在 l o g n logn logn的时间内找到最短的边,大大加快了最短路运行的时间,整体时间复杂度降到 n l o g n nlogn nlogn

优先队列要重载。默认是从大到小,要从小到大需要重载运算符。

SPFA

容易死。

#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
#define maxn 10005
#define maxm 30005

struct node {
    int to, nxt, w;
}edge[maxm << 1];
int head[maxn], cnt, n, m, tmpa, tmpb, tmpc;
int dist[maxn], q;
bool vis[maxn];
    
void add(int u, int v, int w) {
    edge[++cnt].to = v;
    edge[cnt].nxt = head[u];
    edge[cnt].w = w;
    head[u] = cnt;
}
    
void spfa(int s) {
    memset(vis, false, sizeof(vis));
    for(register int i = 1; i <= n; ++i)
        dist[i] = 2147483647;
    queue<int> Q;
    dist[s] = 0, vis[s] = true;
    Q.push(s);
    while(!Q.empty()) {
        int u = Q.front();
        Q.pop(), vis[u] = false;
        for(register int i = head[u]; i; i = edge[i].nxt) {
            int v = edge[i].to, w = edge[i].w;
            if(dist[v] > dist[u] + w) {
                dist[v]  = dist[u] + w;
                if(!vis[v]) {
                    vis[v] = true;
                    Q.push(v);
                }
            }
        }
    }
}
    
int main() {
    cin >> n >> m;
    for(register int i = 1; i <= m; ++i) {
        cin >> tmpa >> tmpb >> tmpc;
        add(tmpa, tmpb, tmpc);
        add(tmpb, tmpa, tmpc);
    }
    cin >> q;
    for(register int i = 1; i <= q; ++i) {
        cin >> tmpa >> tmpb;
        spfa(tmpa);
        cout << dist[tmpb] << std::endl;
    }
}

跟优先队列优化的 D i j k s t r a Dijkstra Dijkstra差不多。但是这个可以判断负环。怎么判断?如果某个点进队的次数甚至大于了 n n n那就说明肯定有负环

#include <iostream>
#include <stdio.h>
#include <queue>
#include <cstring>
#define maxn 100005
using namespace std;

struct node {
    int to, nxt, w;
}edge[maxn >> 1];
int head[maxn], cnt, appCount[maxn];
bool vis[maxn], flag;

void add(int u, int v, int w) {
    edge[++cnt].to = v;
    edge[cnt].w = w;
    edge[cnt].nxt = head[u];
    head[u] = cnt;
}

int n, m, s, dist[maxn], t;

void SPFA(int s) {
    queue<int> Lily;
    for(register int i = 1; i <= n; ++i)
        dist[i] = 2147483647;
    memset(vis, false, sizeof(vis));
    dist[s] = 0;
    Lily.push(s);
    vis[s] = true;
    appCount[s] = 1;
    while(!Lily.empty()) {
        int f = Lily.front();
        Lily.pop();
        vis[f] = false;
        for(register int i = head[f]; i; i = edge[i].nxt) {
            int v = edge[i].to;
            int w = edge[i].w;
            if(dist[v] > dist[f] + w) {
                dist[v] = dist[f] + w;
                appCount[v] = appCount[f] + 1;
                if(appCount[v] > n) {
                    flag = true;
                    return;
                }
                if(!vis[v]) 
                    Lily.push(v);
            }
        }
    }
}

void solve() {
    memset(head, 0, sizeof(head));
    memset(vis, false, sizeof(vis));
    memset(appCount, 0, sizeof(appCount));
    flag = false;
    cnt = 0;
    cin >> n >> m;
    int tmpa, tmpb, tmpc;
    for(register int i = 1; i <= m; ++i) {
        cin >> tmpa >> tmpb >> tmpc;
        add(tmpa, tmpb, tmpc);
        if(tmpc >= 0)
            add(tmpb, tmpa, tmpc);
    }
    SPFA(1);
    if(flag) cout << "YE5" << endl;
    else cout << "N0" << endl;
    return;
}

int main() {
    cin >> t;
    memset(head, 0, sizeof(head));
    memset(vis, false, sizeof(vis));
    memset(appCount, 0, sizeof(appCount));
    cnt = 0;
    while(t--) solve();
    return 0;
}

最小生成树

优先队列优化的Prim

#include <iostream>
#include <queue>
using namespace std;

const int maxn = 100005;
const int inf = 2147483647;

struct node {
    int to, next, w;
}edge[maxn << 2];
struct node1 {
    int u, d;
    bool operator < (const node1 &a)const {
        return d > a.d;
    }
};
int head[maxn], cnt;
int dis[maxn], n, m, tot, ans;
bool vis[maxn];

void add(int u, int v, int w) {
    edge[++cnt].to = v;
    edge[cnt].next = head[u];
    edge[cnt].w = w;
    head[u] = cnt;
}

inline int min(int a, int b) {
    if(a < b) return a;
    else return b;
}

priority_queue<node1> Q;

void Prim() {
    for(int i = 1; i <= n; i++) dis[i] = inf;
    dis[1] = 0;
    Q.push((node1){1, 0});
    while(!Q.empty() && tot < n) {
        node1 a = Q.top();
        Q.pop();
        int uu = a.u;
        int dd = a.d;
        if(vis[uu]) continue;
        tot++;
        ans += dd;
        vis[uu] = true;
        for(int i = head[uu]; i; i = edge[i].next)
            if(dis[edge[i].to] > edge[i].w) {
                dis[edge[i].to] = edge[i].w;
                Q.push((node1){edge[i].to, dis[edge[i].to]});
            }
    }
}

int main() {
    cin >> n >> m;
    for(int i = 1; i <= m; i++) {
        int tmpa, tmpb, tmpc;
        cin >> tmpa >> tmpb >> tmpc;
        add(tmpa, tmpb, tmpc);
        add(tmpb, tmpa, tmpc);
    }
    Prim();
    if(tot < n) {
        cout << "orz" << endl;
        return 0;
    }
    cout << ans << endl;
    return 0;
}

Kruskal

就是贪心选择最小的点,没啥好说的。

用并查集维护。

#include <iostream>
#include <algorithm>
using namespace std;
#define maxn 10005
#define maxm 30005

struct node {
    int x, y, w;
}edge[maxm];
int n, m, tmpa, tmpb, tmpc, ans;
int f[maxn];
    
bool cmp(node a, node b) {
    return a.w < b.w;
}
    
int find(int x) {
    if(x == f[x]) return x;
    f[x] = find(f[x]);
    return f[x];
}
    
int main() {
    cin >> n >> m;
    for(register int i = 1; i <= m; ++i) {
        cin >> tmpa >> tmpb >> tmpc;
        edge[i].x = tmpa;
        edge[i].y = tmpb;
        edge[i].w = tmpc;
    }
    for(register int i = 1; i <= n; ++i)
        f[i] = i;
    sort(edge + 1, edge + 1 + n, cmp);
    int k = 0;
    for(register int i = 1; i <= m; ++i) {
        int a = find(edge[i].x);
        int b = find(edge[i].y);
        if(a == b) continue;
        ans += edge[i].w;
        k++;
        f[a] = b;
        f(k == n - 1) break;
    }
    if(k < n - 1) {
        cout << "????" << endl;
        return 0;
    }
    cout << ans << endl;
    return 0;
}


另外,关于次小生成树:

先求最小生成树 T T T,枚举添加不在 T T T中的边,则添加后一定会形成环,找到环上边值第二大的边,把它删掉,计算当前生成树的权值,取所有枚举修改的生成树的最小值,即为次小生成树。这种方法的实现更为简单,首先求最小生成树T,然后从每个结点 u u u,遍历最小生成树 T T T,用一个二维的数组 m a x [ u ] [ v ] max[u][v] max[u][v]记录结点u到结点v的路径上边的最大值,然后枚举不在T中的边 ( u , v ) (u,v) (u,v),计算 T − m a x [ u ] [ v ] + w ( u , v ) T-max[u][v]+w(u,v) Tmax[u][v]+w(u,v)的最小值,即为次小生成树的权值 ,这种方法的时间复杂度为 O ( n 2 + e ) O(n^2+e) O(n2+e)

Tarjan

我用的主要作用就是缩点。
缩点咯

#include <iostream>
#include <stdio.h>
using namespace std;
#define maxn 100005
#define maxm 300005

inline int min(int a, int b) {
    if(a < b) return a;
    return b;
}
inline int read() {
    int x = 0, f = 1;
    char c = '\0';
    while(c > '9' || c < '0') {
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9') {
        x = x * 10 + c - '0';
        c = getchar();
    }
    return x * f;
}
    
struct node {
    int to, nxt, w;
}edge[maxm << 1];
int head[maxn], cnt, n, m, tmpa, tmpb, tmpc;
int DFN[maxn], LOW[maxn], stack[maxn], top, tot, belong[maxn], sum;
bool vis[maxn];
    
void add(int u, int v, int w) {
    edge[++cnt].to = v;
    edge[cnt].nxt = head[u];
    edge[cnt].w = w;
    head[u] = cnt;
}
    
void Tarjan(int x) {
    DFN[x] = LOW[x] = ++tot;
    stack[++top] = x;
    vis[x] = true;
    for(register int i = head[x]; i; i = edge[i].nxt) {
        int v = edge[i].to;
        if(!DFN[v]) Tarjan(v), LOW[x] = min(LOW[x], LOW[v]);
        if(vis[v])
            LOW[x] = min(LOW[x], DFN[v]);
        if(DFN[x] == LOW[x]) {
            sum++;
            do {
                belong[stack[top]] = sum;
                vis[stack[top]] = false;
                top--;
            }while(x != stack[top + 1]);
        }
    }
}
    
int main() {
    n = read(); m = read();
    for(register int i = 1; i <= m; ++i) {
        tmpa = read(); tmpb = read(); tmpc = read();
        add(tmpa, tmpb, tmpc);
        add(tmpb, tmpa, tmpc);
    }
    for(register int i = 1; i <= n; ++i)
        if(!DFN[i]) Tarjan(i);
    return 0;
}

树链剖分

……

#include <iostream>
#include <cstdio>
#define maxn 1000005
using namespace std;
#define endl "\n"

inline int read() {
    int x = 0, f = 1;
    char c = '\0';
    while(c > '9' || c < '0') {
    	if(c == '-') f = -1;
    	c = getchar();
	}
    while(c >= '0' && c <= '9') {
        x = x * 10 + c - '0';
        c = getchar();
	}
    return x * f;
}

struct node {
    int to, nxt;
}edge[maxn << 1];
int head[maxn], cnt, ct;
int t, p;
struct xx {
    int sum, lc, rc, tag;
}a[maxn];

void add(int u, int v) {
    edge[++cnt].to = v;
    edge[cnt].nxt = head[u];
    head[u] = cnt;
}

int n, m, res;
int f[maxn], depth[maxn], son[maxn], size[maxn], rk[maxn], top[maxn], id[maxn], w[maxn];

void Archmage(int x, int fa, int dp) {
	depth[x] = dp;
	size[x] = 1;
	f[x] = fa;
	for(register int i = head[x]; i; i = edge[i].nxt) {
	    int v = edge[i].to;
	    if(v == fa) continue;
	    Archmage(v, x, dp + 1);
	    size[x] += size[v];
	    if(size[v] > size[son[x]])
	        son[x] = v;
	}
}

void Archmage_(int x, int t) {
	top[x] = t;
	id[x] = ++ct;
	rk[ct] = w[x];
	if(!son[x]) return;
	Archmage_(son[x], t);
	for(register int i = head[x]; i; i = edge[i].nxt) {
	    int v = edge[i].to;
	    if(v != son[x] && v != f[x])
	        Archmage_(v, v);
	}
}

void pushup(int u) {
	a[u].sum = (a[a[u].lc].sum + a[a[u].rc].sum) % p;
}

void pushdown(int u, int l, int r){
	int mid = (l + r) >> 1;
	a[a[u].lc].sum = (a[a[u].lc].sum + (mid - l + 1) * a[u].tag) % p;
	a[a[u].lc].tag = (a[a[u].lc].tag + a[u].tag) % p;
	a[a[u].rc].sum = (a[a[u].rc].sum + (r - mid) * a[u].tag) % p;
	a[a[u].rc].tag = (a[a[u].rc].tag + a[u].tag) % p;
	a[u].tag = 0;
}

void build(int u, int l, int r) {
	if(l == r) {
	    a[u].sum = (rk[l]) % p;
	    return;
	}
    a[u].lc = ++t;
	int mid = (l + r) >> 1;
	build(a[u].lc, l, mid);
	a[u].rc = ++t;
	build(a[u].rc, mid + 1, r);
	pushup(u);
}

void query(int u, int l, int r, int ll, int rr){
	if(l == ll && r == rr){
	    res = (res + a[u].sum) % p;
	    return;
	}
	int mid = (l+r)/2;
	pushdown(u,l,r);
	if(rr <= mid) query(a[u].lc, l, mid, ll, rr);
	else if(ll > mid) query(a[u].rc, mid+1, r, ll, rr);
	else{
	    query(a[u].lc, l, mid, ll, mid);
	    query(a[u].rc, mid+1, r, mid+1, rr);
	}
}

void update(int u, int l, int r, int ll, int rr, int w) {
	if(l == ll && r == rr){
	    a[u].sum = (a[u].sum + (r - l + 1) * w) % p;
	    a[u].tag = (a[u].tag + w) % p;
	    return;
	}
	int mid = (l + r) >> 1;
	pushdown(u,l,r);
	if(ll > mid) update(a[u].rc, mid + 1, r, ll, rr, w);
	else if(rr <= mid) update(a[u].lc, l, mid, ll, rr, w);
	else{
	    update(a[u].lc, l, mid, ll, mid, w);
	    update(a[u].rc, mid + 1, r, mid + 1, rr, w);
	}
	pushup(u);
}

int qRange(int x, int y) {
	int ans = 0;
	while(top[x] != top[y]) {
	    if(depth[top[x]] < depth[top[y]])
	        swap(x, y);
	    res = 0;
	    query(1, 1, n, id[top[x]], id[x]);
	    ans = (ans + res) % p;
	    x = f[top[x]];
	}
	if(depth[x] > depth[y])
	    swap(x, y);
	res = 0;
	query(1, 1, n, id[x], id[y]);
	ans = (ans + res) % p;
	return ans;
}	

void uRange(int x, int y, int k) {
	while(top[x] != top[y]) {
	    if(depth[top[x]] < depth[top[y]])
	        swap(x, y);
	    update(1, 1, n, id[top[x]], id[x], k % p);
   		x = f[top[x]];
 	}
    if(depth[x] > depth[y])
	    swap(x, y);
	update(1, 1, n, id[x], id[y], k % p);
}

int qSon(int x) {
	res = 0;
	query(1, 1, n, id[x], id[x] + size[x] - 1);
	return res % p;
}

void uSon(int x, int k) {
	update(1, 1, n, id[x], id[x] + size[x] - 1, k % p);
}

int r;

void main() {
	t = 1;
	n = read(); m = read(); r = read(); p = read();
	int tmpa, tmpb, tmpc, tmpd;
	for(register int i = 1; i <= n; ++i)
	    w[i] = read();
	for(register int i = 1; i < n; ++i) {
	    tmpa = read(); tmpb = read();
	    add(tmpa, tmpb);
	    add(tmpb, tmpa);
	}
	Archmage(r, 0, 1);
	Archmage_(r, r);
	build(1, 1, n);
	for(register int i = 1; i <= m; ++i) {
	    tmpa = read();
	    if(tmpa == 1) {
	        tmpb = read(); tmpc = read(); tmpd = read();
	        uRange(tmpb, tmpc, tmpd);
	    }
	    else if(tmpa == 2) {
	        tmpb = read(); tmpc = read();
	        cout << (qRange(tmpb, tmpc)) % p << endl;
	    }
	    else if(tmpa == 3) {
	        tmpb = read(); tmpc = read();
	        uSon(tmpb, tmpc);
	    }
	    else {
	        tmpb = read();
	        cout << (qSon(tmpb)) % p << endl;
	    }
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值