Loading

最短路板子

链式前项星:

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
}
View Code

读入挂(说真的,图论题不用读入挂真的是找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;
}
View Code

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则入队
        }
    }
}
View Code

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\n",dis[n]);
    }
}
View Code

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;
}
View Code

 

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;
}
View Code

 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;
}
View Code

求不带负权的环花费的算法,可以改进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;
}
View Code

 

多源最短路:

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;
}
View Code

Johnson算法 O(nmlog⁡m)

#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\n");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;
}
View Code

 

posted @ 2020-05-25 18:30  ViKyanite  阅读(218)  评论(0编辑  收藏  举报