一、线段树优化的
优先队列版本已经烂大街了,这里就不贴了,而且在下面的分层图里有写
1.普通线段树
时间和内存均是优先队列优化版本的
int n, m; struct edge { int to, w, nxt; edge() {} edge(int t, int ww, int nn) {to = t, w = ww, nxt = nn;} }e[maxn << 1]; int head[maxn], k = 0; void add(int u, int v, int w) {e[k] = edge(v, w, head[u]); head[u] = k++;} ll ans[maxn]; struct node { ll dis; int x; node() {} node(ll d, int xx) {dis = d, x = xx;} }dis[maxn << 2]; //建树初始化,主要是编号也要返回所以要先预处理一下 void build(int p, int l, int r) { if(l == r) {dis[p].x = l; return;} int mid = l + r >> 1; build(p << 1, l, mid); build(p << 1 | 1, mid + 1, r); dis[p].x = dis[p << 1].x; } void change(int p, int l, int r, int x, int y) { if(l == r) {dis[p].dis = y; return;} int mid = l + r >> 1; if(x <= mid) change(p << 1, l, mid, x, y); else change(p << 1 | 1, mid + 1, r, x, y);//单点修改的板子操作 if(dis[p << 1].dis < dis[p << 1 | 1].dis) dis[p] = dis[p << 1]; else dis[p] = dis[p << 1 | 1]; } //因为用距离得到最小,但是需要的是编号,所以返回node node ask(int p, int l, int r, int ls, int rs) { if(ls <= l && r <= rs) {return dis[p];} int mid = l + r >> 1; node ans = node(inf, 0), tmp; if(ls <= mid) ans = ask(p << 1, l, mid, ls, rs); if(rs > mid) { node tmp = ask(p << 1 | 1, mid + 1, r, ls, rs); if(ans.dis > tmp.dis) ans = tmp; } return ans; } int S; void dij() { for(int k = 1; k < n; k++) {//n-1次够用的。虽然我也不知道为什么最后n次跑的比n-1次还要快…… register int u = ask(1, 1, n, 1, n).x; for(int i = head[u]; ~i; i = e[i].nxt) { register int v = e[i].to; if(ans[u] + e[i].w < ans[v]) {//最短路更新 ans[v] = ans[u] + e[i].w, change(1, 1, n, v, ans[v]);//单点修改 } } change(1, 1, n, u, inf);//取出来过后要赋值INF,以免再次取用 } } int main() { memset(head, -1, sizeof head); n = read(), m = read(), S = read(); for(int u, v, w, i = 1; i <= m; i++) u = read(), v = read(), w = read(), add(u, v, w); //初始化 for(int i = 1; i <= (n << 2); i++) dis[i].dis = inf; for(int i = 1; i <= n; i++) ans[i] = inf; //线段树初始化,dis是线段树,ans是答案 build(1, 1, n); change(1, 1, n, S, 0); ans[S] = 0; dij(); for(int i = 1; i <= n; i++) printf("%lld ", ans[i]); return 0; }
2.最强的zkw线段树优化版本
时间和内存均是优先队列优化版本的 %%%
#define rep(I, A, B) for (int I = (A); I <= (B); ++I) #define dwn(I, A, B) for (int I = (A); I >= (B); --I) #define erp(I, X) for (int I = head[X]; I; I = next[I]) const int maxn = 1e5 + 207, maxm = 2e5 + 207, inf = INT_MAX; int v[maxm], w[maxm], head[maxn], next[maxm], tot; int dist[maxn], mp[maxn << 2], M = 1; int n, m, s; template <typename T> inline void read(T& t) { int f = 0, c = getchar(); t = 0; while (!isdigit(c)) f |= c == '-', c = getchar(); while (isdigit(c)) t = t * 10 + c - 48, c = getchar(); if (f) t = -t; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + 48); } inline void ae(int x, int y, int z) { v[++tot] = y; w[tot] = z; next[tot] = head[x]; head[x] = tot; } inline int cmp(int a, int b) { return dist[a] < dist[b] ? a : b; } inline void build(int n) { while (M < n + 2) M <<= 1; mp[0] = n + 1; } inline void modify(int x, int nv) { for (int i = x + M; dist[mp[i]] > nv; i >>= 1) mp[i] = x; dist[x] = nv; } inline void del(int x) { for (mp[x += M] = 0, x >>= 1; x; x >>= 1) mp[x] = cmp(mp[x << 1], mp[x << 1 | 1]); } inline void dijkstra(int s) { rep(i, 0, n) dist[i] = inf; build(n); modify(s, 0); rep(t, 1, n - 1) { int x = mp[1]; del(x); erp(i, x) if (dist[v[i]] > dist[x] + w[i]) modify(v[i], dist[x] + w[i]); } } int main() { read(n),read(m),read(s); rep(i, 1, m) { int x, y, z; read(x),read(y),read(z); ae(x, y, z); } dijkstra(s); rep(i, 1, n) write(dist[i]), putchar(' '); puts(""); return 0; }
3.路径还原
白书上的代码,没有优化是,正序输出最短路径
/*如果需要输出路径 可以用一个prev[ j ]来记录最短路上顶点 j 的前驱, 那么在o(|V|)的时间内完成最短路径的恢复。 在d[ j ]被d[ j ] = d[ k ] + cost[ k ][ j ]更新时, 修改prev[ j ] = k,这样就可以求出来prev的数组, 可以在以上算法中用路径前驱标记法来还原出来路径。*/ int cost[MAX_V][MAX_V]; //cost[u][v]表示边e=(u, v)的权值(不存在这条边时设为INF) int d[MAX_V]; //从顶点s出发的最短距离 bool used[MAX_V]; //已经使用过的图中的点 int V; //顶点数 int prev[MAX_V]; //记录前驱点 //从s出发到各个顶点的距离 void dijkstra(int s) { fill(d, d + V, INF); //初始化 fill(used, used + V, false); //初始化 fill(prev, prev + V, -1); d[s] = 0; while(true) { int v = -1; for(int u = 0; u < V; u++){ if(!used[u] && (v == -1 || d[u] < d[v])) v = u; // 从尚未使用过的顶点中选择一个距离最小的顶点 } if(v == -1) break; //没有可跟新的了,结束 used[v] = true; for(int u = 0; u < V; u++){ d[u] = min(d[u], d[v] + cost[u][v]); //因为加入了一个点V所以所有的d都要再更新一遍 prev[u] = v; } } } vector<int> get_path(int t) //到顶点t的最短路 { vector<int> path; for(; t != -1; t = prev[t]) path.push_back(t); reverse(path.begin(), path.end()); return path; }
二、可以处理负权值的
SPFA要谨慎使用!
int nex[M],ver[M],head[N],edge[M],tot; void add(int u,int v,int val){ ver[++tot] = v; edge[tot] = val; nex[tot] = head[u]; head[u] = tot; } queue<int>q; bool vis[N]; int d[N]; int n,m; void spfa(int s){ //memset(d,0x3f,sizeof d); for(int i = 1; i<= n;++i) d[i] = 2147483647; memset(vis,0,sizeof vis); d[s] = 0; vis[s] = 1; q.push(s); while(q.size()){ int x = q.front(); q.pop(); vis[x] = 0; //扫描所有出边 for(int i = head[x];i;i = nex[i]){ int y = ver[i]; int z = edge[i]; if(d[y] > d[x] + z){ d[y] = d[x] + z; if(!vis[y])//不在堆里就入堆并标记一下 q.push(y),vis[y] = 1; } } } } int s; int main() { cin>>n>>m>>s; over(i,1,m){ int x,y,z; scanf("%d%d%d",&x,&y,&z); add(x,y,z); } spfa(s); over(i,1,n){ printf("%d ",d[i]); } return 0; }
三、两种分层图最短路
分层次
分成层图
const int INF = 0x3f3f3f3f; const int N = 6e5+7; int nex[N],ver[N],head[N],edge[N],tot; int n,m,k,s,t; int dist[N]; bool vis[N]; void init(){ memset(head,-1,sizeof head); tot = 0; memset(dist,0x3f,sizeof dist); memset(vis,0,sizeof vis); } void add(int u,int v,int val){ ver[++tot] = v; edge[tot] = val; nex[tot] = head[u]; head[u] = tot; } void dijkstra(){ priority_queue<PII,vector<PII>,greater<PII> >q; dist[1] = 0; //vis[s] = 1;dijkstra和spfa不一样!这里不用标记,不然就走不动啦 q.push({dist[1],1}); while(q.size()){ PII now = q.top();//这种声明的变量后面不要用逗号运算符!!! q.pop(); int u = now.second; if(vis[u])continue; vis[u] = 1; for(int i = head[u]; i != -1 ;i = nex[i]){ int v = ver[i],val = edge[i]; if(!vis[v] && dist[v] > dist[u] + val){ dist[v] = dist[u] + val; q.push({dist[v],v}); } } } } int main() { init(); scanf("%d%d%d",&n,&m,&k); over(i,1,m){ int x,y,z; scanf("%d%d%d",&x,&y,&z); for(int j = 0;j <= k;++j){ add(x + j * n,y + j * n,z);//这一层和这一层连起来 add(y + j * n,x + j * n,z); if(j != k){//只要还有就这一层和下一层连起来,并且权值为0 add(x + j * n,y + (j + 1) * n,0); add(y + j * n,x + (j + 1) * n,0); } } } dijkstra(); int ans = INF;//dijkstra只是更新了一个dis数组,答案需要我们自己找 over(i,0,k) ans = min(ans,dist[n + i * n]); printf("%d\n",ans); return 0; }
分维度
这里是因为题目要求的答案是第k+1的权值最小,所以只求单条路径的权值即可,但是这样的更新并不能满足dijkstra的贪心性质,所以我们要选择SPFA算法。
/*POJ 3662 Telephone Lines*/ const int N = 3e3; const int M = 5e4+7;//注意这里数据范围要用M int d[N][N]; int n,m,k; int nex[M],ver[M],head[N],edge[M],tot; void add(int u,int v,int val){ ver[++tot] = v; edge[tot] = val; nex[tot] = head[u]; head[u] = tot; } queue<int>q; bool vis[N]; void spfa(int s){ memset(d,0x3f,sizeof d); memset(vis,0,sizeof vis); d[s][0] = 0; vis[s] = 0; q.push(s); while(q.size()){ int x = q.front(); q.pop(); vis[x] = 0; for(int i = head[x];i;i = nex[i]){ int y = ver[i],z = edge[i]; int w = max(d[x][0],z);//不同于往常的是每一条路径的 最大边 是这条路径的花费 if(d[y][0]>w){//先算普通的最短路 d[y][0] = w; if(!vis[y]) q.push(y),vis[y] = 1; } //反正就是动规,暴力所有状态转移取最小,状态转移的方式就是可以免费。 //每次对每种路径都取一次最优 for(int p = 1;p <= k;++p){//然后再看免费的,不一定是几条边最优//不管怎么说肯定是从前往后地推出的 int w = min(d[x][p-1],max(d[x][p],z));//使最大边最小//主要这里的答案都是一条边的权值 //当前的状态是从x转移到y,其中这条边免费或者不免费 if(d[y][p] > w){ d[y][p] = w; if(!vis[y]) q.push(y),vis[y] = 1; } } } } } int main(){ cin>>n>>m>>k; over(i,1,m){ int x,y,z; scanf("%d%d%d",&x,&y,&z); add(x,y,z); add(y,x,z); } spfa(1); int ans = 1e9+7; over(i,0,k){//注意是从0开始,因为也可以不选 //就真答案并不在掌握之中 //其实就是分层图,应该是 k + 1 层 ans = min(ans,d[n][i]); } if(ans == 1e9+7) puts("-1"); else printf("%d\n",ans); return 0; }
四、Floyd算法,传递闭包
Floyd算法
设表示“经过若干个编号不超过k的结点”从i到j的最短路长度。
Floyd算法的实质是动态规划。
k是阶段,所以必须放在最外层循环。
i和j是附加状态,应该放置于内层循环。
int dis[1000][1000],n,m; int main() { cin>>n>>m; memset(dis,0x3f,sizeof dis); for(int i = 1;i <= n;++i) dis[i][i] = 0; for(int i = 1;i <= m;++i){ int x,y,z; scanf("%d%d%d",&x,&y,&z); dis[x][y] = min(dis[x][y],z);//可能会重复 } 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]); for(int i = 1;i <= n;++i) for(int j = 1;j <= n;++j) printf("%d ",dis[i][j]); return 0; }
使用Floyd实现的传递闭包
int dis[1000][1000],n,m; int main() { cin>>n>>m; for(int i = 1;i <= n;++i) dis[i][i] = 1; for(int i = 1;i <= m;++i){ int x,y; scanf("%d%d",&x,&y); dis[x][y] = dis[y][x] = 1; } for(int k = 1;k <= n;++k) for(int i = 1;i <= n;++i) for(int j = 1;j <= n;++j) dis[i][j] |= dis[i][k] & dis[k][j]; //把整个关系梳理出来 }