最短路专题练习

时间:2019-06-12
本文章向大家介绍最短路专题练习,主要包括最短路专题练习使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

POJ3662 Telephone Lines

一共有N个电线杆,有P对电线杆是可以连接的,用几条线连接在一起的电线杆之间都可相互通信,现在想要使得电线杆1和电线杆N能相互通信,并且电线公司提出K条电线是可以免费使用的,当使用电线的数量超过K条,超出的电线要收费,收的总费用为去掉免费使用的K条电线之后最长的那条电线的长度。现在需要尽可能的减少费用,问最少费用是多少



广义最短路问题,(x,p)表示从1到x,指定p条路径免费时,最小花费是多少。这样若\((x,y,z)\in E\),则用max(D[x,p],z)更新D[y,p]的最小值,用D[x,p]更新D[y,p+1]的最小值。

时间复杂度\(O(tNP)\),这题没有卡SPFA。

#include<iostream>
#include<cstring>
#include<queue>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
    rg T data=0,w=1;rg char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-') w=-w;
    for(;isdigit(ch);ch=getchar()) data=data*10+ch-'0';
    return data*w;
}
template<class T>il T read(rg T&x) {return x=read<T>();}
typedef long long ll;
using namespace std;

co int N=1e3+1,P=2e4+1,INF=0x3f3f3f3f;
int n,p,k,d[N*N];
int Head[N*N],Edge[N*P],Next[N*P],Leng[N*P],tot=0;
bool v[N*N];
queue<int> q;
void add(int x,int y,int z){
    Edge[++tot]=y,Leng[tot]=z,Next[tot]=Head[x],Head[x]=tot;
}
int main(){
    read(n),read(p),read(k);
    for(int i=1,x,y,z;i<=p;++i){
        read(x),read(y),read(z);
        for(int j=0;j<=k;++j){
            add(x+j*n,y+j*n,z);
            add(y+j*n,x+j*n,z);
        }
        for(int j=0;j<k;++j){
            add(x+j*n,y+(j+1)*n,0);
            add(y+j*n,x+(j+1)*n,0);
        }
    }
// spfa
    memset(d,0x3f,sizeof d);
    d[1]=0;
    q.push(1),v[1]=1;
    for(int x;q.size();q.pop()){
        x=q.front(),v[x]=0;
        for(int i=Head[x],y,z;i;i=Next[i]){
            y=Edge[i],z=Leng[i];
            if(d[y]>max(d[x],z)){
                d[y]=max(d[x],z);
                if(!v[y]) q.push(y),v[y]=1;
            }
        }
    }
    printf("%d\n",d[(k+1)*n]==INF?-1:d[(k+1)*n]);
    return 0;
}

实际上这题可以二分答案做,那才是有复杂度保证的做法。

CH6101 最优贸易

C国有 n 个大城市和 m 条道路,每条道路连接这 n 个城市中的某两个城市。任意两个城市之间最多只有一条道路直接相连。这 m 条道路中有一部分为单向通行的道路,一部分为双向通行的道路,双向通行的道路在统计条数时也计为1条。
C国幅员辽阔,各地的资源分布情况各不相同,这就导致了同一种商品在不同城市的价格不一定相同。但是,同一种商品在同一个城市的买入价和卖出价始终是相同的。
商人阿龙来到C国旅游。当他得知“同一种商品在不同城市的价格可能会不同”这一信息之后,便决定在旅游的同时,利用商品在不同城市中的差价赚一点旅费。设C国 n 个城市的标号从 1~n,阿龙决定从1号城市出发,并最终在 n 号城市结束自己的旅行。在旅游的过程中,任何城市可以被重复经过多次,但不要求经过所有 n 个城市。
阿龙通过这样的贸易方式赚取旅费:他会选择一个经过的城市买入他最喜欢的商品——水晶球,并在之后经过的另一个城市卖出这个水晶球,用赚取的差价当做旅费。因为阿龙主要是来C国旅游,他决定这个贸易只进行最多一次,当然,在赚不到差价的情况下他就无需进行贸易。
现在给出 n 个城市的水晶球价格,m 条道路的信息(每条道路所连接的两个城市的编号以及该条道路的通行情况)。请你告诉阿龙,他最多能赚取多少旅费。


这道题解法是,两遍SPFA。
正着一遍记录从起点到当前点遇到的最小点权
倒着一遍记录终点到当前点的最大点权
最后枚举每一个点,答案是max(max[i]-min[i])

#include<iostream>
#include<cstring>
#include<queue>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
    rg T data=0,w=1;rg char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-') w=-w;
    for(;isdigit(ch);ch=getchar()) data=data*10+ch-'0';
    return data*w;
}
template<class T>il T read(rg T&x) {return x=read<T>();}
typedef long long ll;
using namespace std;

co int N=1e5+1,M=1e6+1;
int n,m,tot=0,Price[N];
int Head[N],Side[M],Next[M],ans[N];
int fHead[N],fSide[M],fNext[M],fans[N];
bool v[N],fv[N];
priority_queue<pair<int,int> > q;
priority_queue<pair<int,int> > fq;
int main(){
    read(n),read(m);
    for(int i=1;i<=n;++i) read(Price[i]);
    for(int i=1,x,y,z;i<=m;++i){
        read(x),read(y),read(z);
        Side[++tot]=y,Next[tot]=Head[x],Head[x]=tot;
        fSide[tot]=x,fNext[tot]=fHead[y],fHead[y]=tot;
        if(z==2){
            Side[++tot]=x,Next[tot]=Head[y],Head[y]=tot;
            fSide[tot]=y,fNext[tot]=fHead[x],fHead[x]=tot;
        }
    }
    memset(ans,0x3f,sizeof ans);
    memset(fans,0xcf,sizeof fans);
    ans[1]=Price[1],fans[n]=Price[n];
    q.push(make_pair(-ans[1],1)),fq.push(make_pair(fans[n],n));
    for(int x;q.size();){
        x=q.top().second,q.pop();
        if(v[x]) continue;
        v[x]=1;
        for(int i=Head[x],y;i;i=Next[i]){
            y=Side[i];
            if(ans[y]>ans[x]){
                ans[y]=ans[x],ans[y]=min(ans[y],Price[y]);
                q.push(make_pair(-ans[y],y));
            }
        }
    }
    for(int x;fq.size();){
        x=fq.top().second,fq.pop();
        if(fv[x]) continue;
        fv[x]=1;
        for(int i=fHead[x],y;i;i=fNext[i]){
            y=fSide[i];
            if(fans[y]<fans[x]){
                fans[y]=fans[x],fans[y]=max(fans[y],Price[y]);
                fq.push(make_pair(fans[y],y));
            }
        }
    }
    int Ans=0;
    for(int i=1;i<=n;++i) Ans=max(Ans,fans[i]-ans[i]);
    printf("%d\n",Ans);
    return 0;
}

BZOJ2200 [Usaco2011 Jan]道路和航线

Farmer John正在一个新的销售区域对他的牛奶销售方案进行调查。他想把牛奶送到T个城镇 (1 <= T <= 25,000),编号为1T。这些城镇之间通过R条道路 (1 <= R <= 50,000,编号为1到R) 和P条航线 (1 <= P <= 50,000,编号为1到P) 连接。每条道路i或者航线i连接城镇A_i (1 <= A_i <= T)到B_i (1 <= B_i <= T),花费为C_i。对于道路,0 <= C_i <= 10,000;然而航线的花费很神奇,花费C_i可能是负数(-10,000 <= C_i <= 10,000)。道路是双向的,可以从A_i到B_i,也可以从B_i到A_i,花费都是C_i。然而航线与之不同,只可以从A_i到B_i。事实上,由于最近恐怖主义太嚣张,为了社会和谐,出台 了一些政策保证:如果有一条航线可以从A_i到B_i,那么保证不可能通过一些道路和航线从B_i回到A_i。由于FJ的奶牛世界公认十分给力,他需要运送奶牛到每一个城镇。他想找到从发送中心城镇S(1 <= S <= T) 把奶牛送到每个城镇的最便宜的方案,或者知道这是不可能的。



如果有一条航线可以从Ai到Bi,那么保证不可能通过一些道路和航线从Bi回到Ai如果有一条航线可以从Ai到Bi,那么保证不可能通过一些道路和航线从Bi回到Ai
即不会存在使dijkstra在联通块内无限松弛的情况
根据题中的特殊性质,把所有无向边先加进图中,用dfs缩点,统计每个联通块的入度
这样把有向边加进来以后可以弄成一个DAG
然后在每个联通块内用dijkstra维护最短路 遍历领接表的时候判断一下边的两个端点是否属于同一个联通块
如果不是, 按照拓扑排序的方式更新入度并把入度为0的联通块入队即可

#include<bits/stdc++.h>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
    rg T data=0,w=1;rg char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-') w=-w;
    for(;isdigit(ch);ch=getchar()) data=data*10+ch-'0';
    return data*w;
}
template<class T>il T read(rg T&x) {return x=read<T>();}
typedef long long ll;
using namespace std;

co int maxT=25001,maxRP=150006,INF=0x3f3f3f3f;
int T,R,P,S;
int Head[maxT],Edge[maxRP],Cost[maxRP],Next[maxRP],totS=0;
int c[maxT],deg[maxT],d[maxT],totc=0;
bool v[maxT];
queue<int> q;
priority_queue<pair<int,int> > Q;
void add(int A,int B,int C){
    Edge[++totS]=B,Cost[totS]=C,Next[totS]=Head[A],Head[A]=totS;
}
void dfs(int i){
    for(int j=Head[i];j;j=Next[j])
        if(!c[Edge[j]]) c[Edge[j]]=totc,dfs(Edge[j]);
}
int main(){
    memset(d,0x7f,sizeof d);
    read(T),read(R),read(P),read(S);
    for(int i=1,A,B,C;i<=R;++i){
        read(A),read(B),read(C);
        add(A,B,C),add(B,A,C);
    }
    for(int i=1;i<=T;++i)
        if(!c[i]) c[i]=++totc,dfs(i);
    for(int i=1,A,B,C;i<=P;++i){
        read(A),read(B),read(C);
        add(A,B,C),++deg[c[B]];
    }
    q.push(c[S]);
    for(int i=1;i<=totc;++i)if(!deg[i]) q.push(i);
    d[S]=0;
    for(int i;q.size();){
        i=q.front(),q.pop();
        for(int j=1;j<=T;++j)
            if(c[j]==i) Q.push(make_pair(-d[j],j));
        for(int x;Q.size();){
            x=Q.top().second,Q.pop();
            if(v[x]) continue;
            v[x]=1;
            for(int j=Head[x],y;j;j=Next[j]){
                y=Edge[j];
                if(d[x]+Cost[j]<d[y]){
                    d[y]=d[x]+Cost[j];
                    if(c[x]==c[y]) Q.push(make_pair(-d[y],y));
                }
                if(c[x]!=c[y]&&!--deg[c[y]]) q.push(c[y]);
            }
        }
    }
    for(int i=1;i<=T;++i){
        if(d[i]>INF) puts("NO PATH");
        else printf("%d\n",d[i]);
    }
    return 0;
}

POJ1094 Sorting It All Out

对于N个大写字母,给定它们的一些关系,要求判断出经过多少个关系之后可以确定它们的排序或者排序存在冲突,或者所有的偏序关系用上之后依旧无法确定唯一的排序,样例数据已经说得够详细了。

  1. 如果存在则输出
    操作数以及唯一拓扑序列
  2. 如果不存在则输出
    拓扑序列不唯一确定(很显然 如果存在不唯一入度为0的点就出现这种状况)
    在第几步出现环

这题还可以通过floyd做,将距离数组定义为关系数组就行了,还可以简化代码。
另外输入输出格式真烦。

#include<iostream>
#include<algorithm>
#include<cstring>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
    rg T data=0,w=1;rg char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-') w=-w;
    for(;isdigit(ch);ch=getchar()) data=data*10+ch-'0';
    return data*w;
}
template<class T>il T read(rg T&x) {return x=read<T>();}
typedef long long ll;
using namespace std;

co int N=30;
int n,m,d[N][N],e[N][N];
int floyd(){
    memcpy(e,d,sizeof e);
    for(int k=0;k<n;++k)
        for(int i=0;i<n;++i)
            for(int j=0;j<n;++j){
                e[i][j]|=e[i][k]&e[k][j];
                if(e[i][j]==e[j][i]&&e[i][j]&&i!=j) return -1;
            }
    for(int i=0;i<n;++i)
        for(int j=0;j<n;++j)
            if(e[i][j]==e[j][i]&&!e[j][i]&&i!=j) return 0;
    return 1;
}
void Sorting_It_All_Out(){
    memset(d,0,sizeof d);
    bool flag=1;
    for(int i=1;i<=m;++i){
        char s[6];scanf("%s",s);
        d[s[0]-'A'][s[2]-'A']=1;
        if(flag){
            int now=floyd();
            if(now==-1){
                printf("Inconsistency found after %d relations.\n",i);
                flag=0;
            }
            else if(now==1){
                printf("Sorted sequence determined after %d relations: ",i);
                pair<int,int> ans[N];
                for(int j=0;j<n;++j) ans[j].first=0,ans[j].second='A'+j;
                for(int j=0;j<n;++j)
                    for(int k=0;k<n;++k)
                        if(e[j][k]) ++ans[j].first;
                sort(ans,ans+n);
                for(int j=n-1;j>=0;--j) putchar(ans[j].second);
                puts(".");
                flag=0;
            }
        }
    }
    if(flag) puts("Sorted sequence cannot be determined.");
}
int main(){
    while(read(n)|read(m)) Sorting_It_All_Out();
    return 0;
}

POJ1734 Sightseeing trip

给出一张无向图,求一个最小环并输出路径。
模板题,理解floyd 的在 i , j 路径中没有包含k(因为此时k未用来更新),即可写出最小环
floyd找最小环,在松弛前找环。



找最小环的时候没加long longWA了,不知道为什么,可能题目数据范围出错了。

#include<iostream>
#include<vector>
#include<cstring>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
    rg T data=0,w=1;rg char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-') w=-w;
    for(;isdigit(ch);ch=getchar()) data=data*10+ch-'0';
    return data*w;
}
template<class T>il T read(rg T&x) {return x=read<T>();}
typedef long long ll;
using namespace std;

co int N=101;
int a[N][N],d[N][N],pos[N][N];
int n,m,ans=0x3f3f3f3f;
vector<int> path;
void get_path(int x,int y){
    if(pos[x][y]==0) return;
    get_path(x,pos[x][y]);
    path.push_back(pos[x][y]);
    get_path(pos[x][y],y);
}
int main(){
    read(n),read(m);
    memset(a,0x3f,sizeof a);
    for(int i=1;i<=n;++i) a[i][i]=0;
    for(int i=1,x,y,z;i<=m;++i){
        read(x),read(y),read(z);
        a[y][x]=a[x][y]=min(a[x][y],z);
    }
    memcpy(d,a,sizeof a);
    for(int k=1;k<=n;++k){
        for(int i=1;i<k;++i)
            for(int j=i+1;j<k;++j)
                if((ll)d[i][j]+a[j][k]+a[k][i]<ans){ // edit 1: long long
                    ans=d[i][j]+a[j][k]+a[k][i];
                    path.clear();
                    path.push_back(i);
                    get_path(i,j);
                    path.push_back(j);
                    path.push_back(k);
                }
        for(int i=1;i<=n;++i)
            for(int j=1;j<=n;++j)
                if(d[i][j]>d[i][k]+d[k][j]){
                    d[i][j]=d[i][k]+d[k][j];
                    pos[i][j]=k;
                }
    }
    if(ans==0x3f3f3f3f) return puts("No solution."),0;
    for(int i=0;i<path.size();++i) printf("%d ",path[i]);
    return 0;
}

POJ3613 Cow Relays

给定一个T(2 <= T <= 100)条边的无向图,求S到E恰好经过N(2 <= N <= 1000000)条边的最短路。
用矩阵来表示两个点之间走k步的最短路,然后快速幂。
时间复杂度\(O(T^3\log N)\)

#include<iostream>
#include<map>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
    rg T data=0,w=1;rg char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-') w=-w;
    for(;isdigit(ch);ch=getchar()) data=data*10+ch-'0';
    return data*w;
}
template<class T>il T read(rg T&x) {return x=read<T>();}
typedef long long ll;
using namespace std;

co int N=201,INF=0x3f3f3f3f;
int n,t,s,e,tot;
map<int,int> m;
struct M{
    int a[N][N];
    void pre(){
        for(int i=1;i<=tot;++i)
            for(int j=1;j<=tot;++j) a[i][j]=INF;
    }
}st,ed;
M mul(co M&a,co M&b){
    static M c;
    c.pre();
    for(int i=1;i<=tot;++i)
        for(int j=1;j<=tot;++j)
            for(int k=1;k<=tot;++k)
                c.a[i][j]=min(c.a[i][j],a.a[i][k]+b.a[k][j]);
    return c;
}
int main(){
    read(n),read(t),read(s),read(e);
    tot=200,st.pre(),tot=0;
    for(int x,y,z;t--;){
        read(z),read(x),read(y);
        x=m[x]?m[x]:(m[x]=++tot);
        y=m[y]?m[y]:(m[y]=++tot);
        st.a[x][y]=st.a[y][x]=z;
    }
    memcpy(ed.a,st.a,sizeof ed.a);
    for(--n;n;n>>=1,st=mul(st,st))
        if(n&1) ed=mul(ed,st);
    printf("%d\n",ed.a[m[s]][m[e]]);
    return 0;
}

POJ3463 Sightseeing

旅行团每天固定的从S地出发到达T地,为了省油要求尽量走最短路径或比最短路径长1单位距离的路径,求满足条件的路径条数



参照Jack Ge的题解。

算法:最短路和次短路。Dijkstra算法。采用邻接表建图。
总结:不要用邻接矩阵。因为有重边。
dis[x][2]:dis[x][0]表示起点到x的最短路、dis[x][1]表示起点到x的次短路;
cnt[x][2]:cnt[x][0]表示起点到x的最短路条数、cnt[x][1]表示起点到x的次短路的条数;
vis[x][2]对应于x和0、1功能为记录该点是否被访问!

那么如何更新最小和次小路呢?显然我们容易想到下面的方法:

  1. if(x<最小)更新最小,次小;
  2. else if(x==最小)更新方法数;
  3. else if(x<次小)更新次小;
  4. else if(x==次小)更新方法数;

最后说说dijkstra的循环部分、这也是本题的关键。为什么我们要循环2nnum-1次?显然这道题中我们每一条边都需要考虑、这不是在求最短的一条,说白了是让你求出所有的可能组合,那么我们势必对每一种情况都需要遍历一次,虽然中间有重复。最短路里已知[start][0]已被标记为访问过,那么就只有nnum-1次遍历了,而次短路我们则需要遍历nnum次,这样两者加起来就为2nnum-1次。这与我们平时使用优先队列+heap是一样的。只是更加细化了而已。

#include<iostream>
#include<cstring>
#include<vector>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
    rg T data=0,w=1;rg char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-') w=-w;
    for(;isdigit(ch);ch=getchar()) data=data*10+ch-'0';
    return data*w;
}
template<class T>il T read(rg T&x) {return x=read<T>();}
typedef long long ll;
using namespace std;

co int N=1e3+1;
int n,m,d[N][2],c[N][2];
bool v[N][2];
vector<pair<int,int> > e[N];

int dijkstra(int s,int t){
    memset(v,0,sizeof v);
    memset(d,0x3f,sizeof d);
    memset(c,0,sizeof c);
    d[s][0]=0,c[s][0]=1;
    for(int i=1;i<n<<1;++i){
        int now=1e9,x=1,w=0;
        for(int j=1;j<=n;++j){
            if(!v[j][0]&&d[j][0]<now)
                now=d[x=j][w=0];
            else if(!v[j][1]&&d[j][1]<now)
                now=d[x=j][w=1];
        }
        if(now==1e9) break;
        v[x][w]=1;
        for(unsigned i=0;i<e[x].size();++i){
            int y=e[x][i].first,z=e[x][i].second;
            if(d[y][0]>now+z){
                d[y][1]=d[y][0],c[y][1]=c[y][0];
                d[y][0]=now+z,c[y][0]=c[x][w];
            }
            else if(d[y][0]==now+z)
                c[y][0]+=c[x][w];
            else if(d[y][1]>now+z)
                d[y][1]=now+z,c[y][1]=c[x][w];
            else if(d[y][1]==now+z)
                c[y][1]+=c[x][w];
        }
    }
    return c[t][0]+(d[t][1]==d[t][0]+1?c[t][1]:0);
}
void Sightseeing(){
    for(int i=1;i<=n;++i) e[i].clear();
    read(n),read(m);
    for(int x,y,z;m--;){
        read(x),read(y),read(z);
        e[x].push_back(make_pair(y,z));
    }
    int s=read<int>(),t=read<int>();
    printf("%d\n",dijkstra(s,t));
}
int main(){
    for(int T=read<int>();T--;) Sightseeing();
    return 0;
}

原文地址:https://www.cnblogs.com/autoint/p/10911235.html