链式前项星:
struct E { int to, w, next; }edge[N]; //这里千万要注意,如果题目是双向边的话,这里的N要开2*N int tot, head[N]; //加边 inline void add_edge(int u, int v, int w) { edge[tot].to = v; edge[tot].w = w; edge[tot].next = head[u]; head[u] = tot++; } //遍历代码 for (int i = head[u]; !i; i=edge[i].next) { int v=edge[i].to; int w=edge[i].w; //to do something }
读入挂(说真的,图论题不用读入挂真的是找T):
inline ll read() { ll x = 0, f = 1; char ch = getchar(); while (ch<'0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); } while ( ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); } return x * f; }
NOTE: 1. 时刻检查全局n与局部n有无重复定义
2. 时刻注意有无初始化就算写了init(),也要看看有没有调用
3. 看一看题目给的点是否从1开始
4. 记住双向边要开两倍maxm
单源最短路:
在加权有向图的最短路径求解算法中,Dijkstra算法只能处理所有边的权值都是非负的图(是否有环不影响求解),
而基于拓扑顺序的算法虽然能在线性时间内高效处理负权重图,但仅局限于无环图。
基于拓扑顺序的算法O(V+E)
void topu_sp(int s) { queue<int> q; while (!q.empty()) q.pop(); memset(dis, inf, sizeof(dis)); dis[s] = 0; for (int i = 1; i <= n; ++ i) if (dg[i] == 0) q.push(i); //入度为0入队列 while (!q.empty()) { int u = q.front(); q.pop(); for (int i = head[u]; ~i; i = egde[i].nex) { int v = edge[i].to; int w = edge[i].w; dis[v] = min(dis[v], dis[u] + w); if (--dg[v] == 0) q.push(v); //将相连的点的入度-1,若出现0则入队 } } }
Dijkstra + 优先队列 O((n+m)logm) (一般不会卡,用就对了)
#include <iostream> #include <cstring> #include <cstdio> #include <algorithm> #include <queue> #include <vector> #define ll long long #define pii pair<int, int> const int inf = 0x3f3f3f3f; const int mod = 1e9+7; const int maxn = 2e5+7; using namespace std; struct node {int to,w,next;} edge[maxn]; int head[maxn], cnt; int dis[maxn], vis[maxn]; int n, m, s, t; inline ll read() { ll x = 0, f = 1; char ch = getchar(); while (ch<'0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); } while ( ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); } return x * f; } void init() { memset(head,-1,sizeof(head)); memset(dis,0x3f,sizeof(dis)); memset(vis,0,sizeof(vis)); cnt = 0; } void add_edge(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 s) { priority_queue<pii,vector<pii>,greater<pii> > q;//从小到大 dis[s] = 0; q.push({dis[s],s}); while(!q.empty()) { int now = q.top().second; q.pop(); if(vis[now]) continue; vis[now] = 1; for(int i = head[now]; i != -1; i = edge[i].next) { int v = edge[i].to; if(dis[v] > dis[now] + edge[i].w) { dis[v] = dis[now] + edge[i].w; q.push({dis[v],v}); } } } } int main() { while(~scanf("%d%d",&m,&n)) { init(); for(int i = 0; i < m; i++) { int u, v, w; scanf("%d%d%d",&u, &v, &w); add_edge(u, v, w), add_edge(v, u, w); } dijkstra(s); printf("%d ",dis[n]); } }
Dijkstra + 斐波那契堆 (待填)
Dijkstra + 配对堆 (比堆快)
View Code
为此还需要一个更为普遍的最短路径求解算法:能够处理负权重图,也能处理有环的情况。
这里注意SPFA容易被卡,如果O(VE)在规定时间内,那么建议使用Bellman-Ford
cir数组是用来标记哪些点在被负权环影响的
Bellman-Ford O(VE)
#include <iostream> #include <cstdio> #include <cmath> #include <cstring> using namespace std; const int N = 500 + 10; const int M = 2500+ 300; const int inf = 0x3f3f3f3f; int dist[N]; int head[N]; bool cir[N]; int n, m, s, tot; struct E { int to, next, w; }edge[M*2]; inline void add_edge(int u, int v, int w) { edge[tot].to = v; edge[tot].w = w; edge[tot].next = head[u]; head[u] = tot++; } void bellman_ford(int s) { dist[s] = 0; for (int i = 1; i <= n; ++ i){ for (int u = 1; u <= n; ++ u){ if (dist[u] == inf) continue; for (int k = head[u]; ~k; k = edge[k].next) { int w = edge[k].w, v = edge[k].to; if (dist[v] > dist[u] + w) { dist[v] = dist[u] + w; if (i == n) cir[u] = cir[v] = 1; } } } } } void init() { tot = 0; memset(head, -1, sizeof(head)); memset(dist, inf, sizeof(dist)); } int main() { ios::sync_with_stdio(0); cin.tie(0); int T; cin >> T; while (T--) { cin >> n >> m >> s; init(); int u, v, w; for (int i = 0; i < m; ++i) { cin >> u >> v >> w; add_edge(u, v, w); add_edge(v, u, w); } for (int i = 0; i < s; ++i) { cin >> u >> v >> w; add_edge(u, v, -w); } bellman_ford(s); } return 0; }
SPFA_DFS
#include<stdio.h> #include<iostream> #include<algorithm> #include<string.h> #include<vector> #include<cmath> #include<string> #include<map> #include<queue> using namespace std; typedef long long ll; const int inf = 0x3f3f3f3f; // 2122219134 const int maxn = 500 + 10; const int maxm = 100000 + 10; struct node { int to; int w; int nex; }edge[maxm]; int tot, n, m, w; int head[maxn], vis[maxn], dis[maxn], cnt[maxn]; void init(){ memset(cnt, 0, sizeof(cnt));//cnt[i]记录i入队的次数 memset(dis, inf, sizeof(dis));//初始化dis memset(head, -1, sizeof(head)); memset(vis, 0, sizeof(vis)); tot = 0; } void add_edge(int u, int v, int w){ edge[tot].to = v; edge[tot].w = w; edge[tot].nex = head[u]; head[u] = tot++; } bool dfs_spfa(int u){ vis[u] = 1; for(int i = head[u]; ~i; i = edge[i].nex) { int v = edge[i].to; if(dis[u] + edge[i].w < dis[v]) { dis[v] = dis[u] + edge[i].w; if (vis[v] || dfs_spfa(v)) { vis[u] = 0; return 1; } } } vis[u] = 0; return 0; } int main(){ int T; ios::sync_with_stdio(0); cin.tie(0); cin >> T; while (T--){ init(); cin >> n >> m >> w; for (int i = 0; i < m; ++ i){ int u, v, w; cin >> u >> v >> w; add_edge(u, v, w), add_edge(v, u, w); } for (int i = 0; i < w; ++ i) { int u, v, w; cin >> u >> v >> w; add_edge(u, v, w * (-1)); } dis[1] = 0; //这个初始化别忘了 if (dfs_spfa(1)) cout << "YES" << endl; else cout << "NO" << endl; } return 0; }
SPFA_BFS
#include<stdio.h> #include<iostream> #include<algorithm> #include<string.h> #include<vector> #include<cmath> #include<string> #include<map> #include<queue> using namespace std; typedef long long ll; const int INF = 0x3f3f3f3f; // 2122219134 const int maxn = 500 + 10; const int maxm = 100000 + 10; struct node { int to; int w; int nex; }edge[maxm]; int tot,n; int head[maxn], dis[maxn], cnt[maxn]; int vis[maxn], cir[maxn]; void dfs(int u) { cir[u] = true; //cir用于标记是否被负环影响 for(int i = head[u]; ~i; i = edge[i].nex) { int v = edge[i].to; if (!cir[v]) dfs(v); } } void init(){ memset(cnt, 0, sizeof(cnt));//cnt[i]记录i入队的次数 memset(dis, INF, sizeof(dis));//初始化dis memset(head, -1, sizeof(head)); memset(vis, 0, sizeof(vis)); memset(cir, 0, sizeof(cir)); tot = 0; } void add(int u, int v, int w){ edge[tot].to = v; edge[tot].w = w; edge[tot].nex = head[u]; head[u] = tot++; } bool bfs_spfa(int s) { dis[s] = 0; queue<int> q; q.push(s); vis[s] = 1; while (!q.empty()) { int u = q.front(); q.pop(); vis[u] = 0; for(int i = head[u]; ~i; i = edge[i].nex){ int v = edge[i].to, w = edge[i].w; if(cir[v]) continue; //cir用于标记是否被负环影响 if(dis[v] > dis[u] + w) { dis[v] = dis[u] + w; if (!vis[v]) { q.push(v); vis[v] = 1; ++cnt[v]; if(cnt[v] >= n) dfs(v); } } } } return 0; } int main(){ int T; ios::sync_with_stdio(0); cin.tie(0); cin >> T; while (T--){ init(); int m, w; cin >> n >> m >> w; for (int i = 0; i < m; ++ i){ int u, v, w; cin >> u >> v >> w; add(u, v, w), add(v, u, w); } for (int i = 0; i < w; ++ i) { int u, v, w; cin >> u >> v >> w; add(u, v, w*(-1)); } if (bfs_spfa(1)) cout << "YES" << endl; else cout << "NO" << endl; } return 0; }
求不带负权的环花费的算法,可以改进spfa算法,初始化d[start] = INF,d[other]=weight(s->other),并将处s外的点入队
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<map> #include<cstdio> #include<queue> #include<stack> using namespace std; const int INF = 0x3f3f3f3f; int cost[305][305]; int dis[305]; int n; bool vis[305]; void spfa( int start ){ stack<int> Q; for( int i = 1; i <= n; i++ ){ dis[i] = cost[start][i]; if( i != start ){ Q.push( i ); vis[i] = true; } else{ vis[i] = false; } } dis[start] = INF; while( !Q.empty() ){ int x = Q.top(); Q.pop(); vis[x] = false; for( int y = 1; y <= n; y++ ){ if( x == y ) continue; if( dis[x] + cost[x][y] < dis[y] ){ dis[y] = dis[x] + cost[x][y]; if( !vis[y] ){ vis[y] = true; Q.push( y ); } } } } } int main(){ ios::sync_with_stdio( false ); while( cin >> n ){ for( int i = 1; i <= n; i++ ){ for( int j = 1; j <= n; j++ ){ cin >> cost[i][j]; } } int ans, c1, cn; spfa( 1 ); ans = dis[n]; c1 = dis[1]; spfa( n ); cn = dis[n]; cout << min( ans, c1 + cn ) << endl; } return 0; }
多源最短路:
Floyd-Warshall算法,中文亦称弗洛伊德算法,是解决任意两点间的最短路径的一种算法,
可以正确处理负权(但不可存在负权回路)的最短路径问题。
(以m=nlogn作为区别稀疏图与稠密图)
Floyd O(n^3) 注意使用时,记得判断重边的情况
bool floyd() { for (int k = 1; k <= n; ++ k) for (int i = 1; i <= n; ++ i){ if (G[i][k] == inf) continue; for (int j = 1; j <= n; ++ j) { if (G[k][j] == inf) continue; if (G[i][j] > G[i][k]+G[k][j]) G[i][j] = G[i][k]+G[k][j]; } if (G[i][i] < 0) return 1; } return 0; }
Johnson算法 O(nmlogm)
#include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #include<cmath> #include<queue> #include<cstdlib> #define N 30010 #define M 60010 const int INF = 0x3f3f3f3f; using namespace std; int n,m,head[N],cnt=0,sum[N]; long long h[N],dis[N]; bool vis[N]; struct Edge{ int nxt,to,val; }ed[M]; int read(){ int x=0,f=1;char c=getchar(); while(c<'0' || c>'9') f=(c=='-')?-1:1,c=getchar(); while(c>='0' && c<='9') x=x*10+c-48,c=getchar(); return x*f; } void add(int u,int v,int w){ ed[++cnt].nxt=head[u]; ed[cnt].to=v,ed[cnt].val=w; head[u]=cnt; return; } void spfa(){ queue<int>q; memset(h,INF,sizeof(h)); memset(vis,false,sizeof(vis)); h[0]=0,vis[0]=true;q.push(0); while(!q.empty()){ int u=q.front();q.pop(); if(++sum[u]>=n){ printf("-1 ");exit(0); } vis[u]=false; for(int i=head[u];i;i=ed[i].nxt){ int v=ed[i].to,w=ed[i].val; if(h[v]>h[u]+w){ h[v]=h[u]+w; if(!vis[v]) q.push(v),vis[v]=true; } } } return; } void dijkstra(int s){ priority_queue<pair<long long,int> >q; for(int i=1;i<=n;i++) dis[i]=INF; memset(vis,false,sizeof(vis)); dis[s]=0; q.push(make_pair(0,s)); while(!q.empty()){ int u=q.top().second;q.pop(); if(vis[u]) continue; vis[u]=true; for(int i=head[u];i;i=ed[i].nxt){ int v=ed[i].to,w=ed[i].val; if(dis[v]>dis[u]+w){ dis[v]=dis[u]+w; if(!vis[v]) q.push(make_pair(-dis[v],v)); } } } return; } int main(){ n=read(),m=read(); int u,v,w; for(int i=1;i<=m;i++) u=read(),v=read(),w=read(),add(u,v,w),add(u,v,w); for(int i=1;i<=n;i++) add(0,i,0); spfa(); //Johnson算法就相当于是给每条边都加上一个权值,跑n遍最短路 //这个权值的来源是先跑一边spfa计算 for(int u=1;u<=n;u++) for(int j=head[u];j;j=ed[j].nxt) ed[j].val+=h[u]-h[ed[j].to]; //当然这里你也可以开二维的dis来记录最短路,方便查询 for(int i=1;i<=n;i++){ dijkstra(i); long long ans=0; for(int j=1;j<=n;j++){ //所以这里要减去原先加上的权值,dis[j]+h[j]-h[i]才是最短路 cout << (dis[j]+h[j]-h[i]) << " "; } cout << endl; } return 0; }