今日主要内容:最短路
一、单源最短路(SSSP)
单源最短路有以下几种算法:
1、Dijkstra(DJ哥斯拉)
我们默认他的起点是1号点(这是可以自己改的)
算法流程:
初始化dis[1] = 0,其他节点:dis[i] = INF(很大);
在没标记的节点中,找一个dis[x]最小的节点x,并将其标记
扫描出边(x,y,z)若dis[y] > dis[x] + z。则用dis[x] + z更新dis[y]
重复上述步骤
那么首先先来看一下他的最原始代码:
int head[100001];
int ecnt = 0;
struct{
int nxt,to,w;
}e[1000001];
void addEdge(int x,int y,int z){
++ecnt;
e[ecnt].nxt = head[x];
e[ecnt].to = y;
e[ecnt].w = w;
head[x] = ecnt;
}//存图
In void dijkstra(int s)
{
memset(dis, 0x3f, sizeof(dis)), dis[1] = 0;
for(int i = 1; i < n; ++i)
{
int now = 0;
for(int j = 1; j <= n; ++j)
if(!vis[j] && dis[j] < dis[now]) now = j;
vis[now] = 1;
for(int j = head[now]; ~j; j = e[j].nxt) //正常图的遍历
{
int v = e[j].to;
dis[v] = min(dis[v], dis[now] + e[j].w) //更新
}
}
}
int main()
{
memset(head, -1, sizeof(head));
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; ++i)
{
int x, y, w; scanf("%d%d%d", &x, &y, &w);
addEdge(x, y, w); //存图函数
}
dijkstra();
for(int i = 1; i <= n; ++i) printf("%d\n", dis[i]);
return 0;
}
但是很显然,这样做的时间复杂度是很高的,所以我们需要一点优化,用优先队列存维护,这样每次直接把最小值取出来然后删除就行了
#include<cstring>
#include<cstdio>
#include<queue>
#include<iostream>
#include<utility>
#include<algorithm>
using namespace std;
typedef pair<int,int> pr;//定义一个pair,pr替pair
struct Edge{
int nxt,to,w;
}e[3000001];
struct cmp
{
bool operator()(const pr p1,const pr p2)
{
return p1.second < p2.second;
}
};//重载运算符,背住
int n,m,s;
int head[3000001];
int ecnt = 0;
int dis[3000001] = { };
priority_queue<pr, vector<pr>, greater<pr> > q;//优先队列
void addEdge(int x,int y,int w){
++ecnt;
e[ecnt].nxt = head[x];
e[ecnt].w = w;
e[ecnt].to = y;
head[x] = ecnt;
}//存图
int vis[3000001] = { };
void dijkstra()
{
memset(dis, 0x3f,sizeof(dis));
dis[s] = 0;
q.push(make_pair(0, s)); //0是距离,出发点到出发点距离是0,s是出发点
while(!q.empty())
{
int now = q.top().second; //now存的是现在点的位置
q.pop();
if(vis[now]) continue;
vis[now] = 1;//标记
for(int i = head[now]; ~i; i = e[i].nxt)//遍历
{
int v = e[i].to;
if(dis[v] > dis[now] + e[i].w)
{
dis[v] = dis[now] + e[i].w;
q.push(make_pair(dis[v], v));
}
}
}
}
int main(){
scanf("%d %d %d" ,&n,&m,&s);
memset(head,-1,sizeof(head));
for(int i = 1;i <= m; i++){
int a,b,c;
scanf("%d %d %d" ,&a,&b,&c);
addEdge(a,b,c);
}
dijkstra();
for(int i = 1;i <= n; i++){
printf("%d " ,dis[i]); //输出到每个点的距离
}
return 0;
}
不难看出,这个哥斯拉是有一定局限性的,就是对边权为负的图无能为力
2、SPFA(Shortest Path Faster Algorithm)
这个算法可以用于边权为负的图,还可以判断负环。
这个其实是用队列优化的Bellman-Ford算法
那么什么事Bellman_Ford算法呢?
简而言之 :给你一个有向图,有一条边(x,y,z),若dis[y] <= dis[x]+z,则该边满足三角不等式,当所有边都满足三角不等式,dis[ ]即为所求
算法的基本流程如下:
队列,初始只有1。
取出对头x,扫描出边(x,y,z)若dis[y] > dis[x] + z,则用dis[x] + z更新dis[y],若y不在队列中,y入队
但是这个算法不是很稳定,容易被有的数据卡掉
这个的代码我也没写
找了个模版记一下吧
In void spfa()
{
Mem(dis, 0x3f), dis[1] = 0;
q.push(1);
while(!q.empty())
{
int now = q.front(); q.pop();
vis[now] = 0;
for(int i = head[now]; ~i; i = e[i].nxt)
{
int v = e[i].to;
if(dis[v] > dis[now] + e[i].w)
{
dis[v] = dis[now] + e[i].w;
if(!vis[v]) q.push(v), vis[v] = 1;
}
}
}
}
二、多源最短路
floyd算法
这个算法的思路基于动态规划
用dp[i][j][k]表示从i->j,在经过的点的编号不大于k的前提下,经过的最短路
状态转移方程:dp[i][j][k] = min(dp[i][j][k],dp[i][k][k - 1] + dp[k][j][k - 1]);
i,j两点间的最短路就是dp[i][j][n]
这个的初始化:dp[i][j][0] = w(i,j)(w是边权)
这个实现就是一个三重循环,但是注意,k的那层要放在最外面,然后是i,最后是j
还有一个二维数组版本的,稍微好记一点,也可用作参考:
for(k=1;k<=n;k++)
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if(e[i][j]>e[i][k]+e[k][j])
e[i][j]=e[i][k]+e[k][j];
好了,接下来就是今天的题了
1、模版。最短路
如题,给出一个有向图,请输出从某一点出发到所有点的最短路径长度。
#include<cstring>
#include<cstdio>
#include<queue>
#include<iostream>
#include<utility>
#include<algorithm>
using namespace std;
typedef pair<int,int> pr;
struct Edge{
int nxt,to,w;
}e[3000001];
struct cmp
{
bool operator()(const pr p1,const pr p2)
{
return p1.second < p2.second;
}
};
int n,m,s;
int head[3000001];
int ecnt = 0;
int dis[3000001] = { };
priority_queue<pr, vector<pr>, greater<pr> > q;
void addEdge(int x,int y,int w){
++ecnt;
e[ecnt].nxt = head[x];
e[ecnt].w = w;
e[ecnt].to = y;
head[x] = ecnt;
}
int vis[3000001] = { };
void dijkstra()
{
memset(dis, 0x3f,sizeof(dis));
dis[s] = 0;
q.push(make_pair(0, s));
while(!q.empty())
{
int now = q.top().second; q.pop();
if(vis[now]) continue;
vis[now] = 1;
for(int i = head[now]; ~i; i = e[i].nxt)
{
int v = e[i].to;
if(dis[v] > dis[now] + e[i].w)
{
dis[v] = dis[now] + e[i].w;
q.push(make_pair(dis[v], v));
}
}
}
}
int main(){
scanf("%d %d %d" ,&n,&m,&s);
memset(head,-1,sizeof(head));
for(int i = 1;i <= m; i++){
int a,b,c;
scanf("%d %d %d" ,&a,&b,&c);
addEdge(a,b,c);
}
dijkstra();
for(int i = 1;i <= n; i++){
if(dis[i] == 1061109567){
printf("2147483647 ");
continue;
}
printf("%d " ,dis[i]);
}
return 0;
}
不再赘述,抄就完了
2、最短路计数
给出一个N个顶点M条边的无向无权图,顶点编号为1−N。问从顶点1开始,到其他每个点的最短路有几条。
这道题都说用bfs,但是昨天也提到了,我bfs真的是一窍不通,所以我还是用哥斯拉硬编的,最后在题解的帮助下我费劲的过了
接下来是代码:
#include<cstring>
#include<cstdio>
#include<queue>
#include<iostream>
#include<utility>
#include<algorithm>
using namespace std;
int dis[3000001] = { };
struct Edge{
int nxt,to,w;
}e[1000001];
int head[100001];
int ecnt = 0;
typedef pair<int,int> pr;//pair变量
struct cmp
{
bool operator()(const pr p1,const pr p2)
{
return p1.second < p2.second;
}
};//重载运算符
void addEdge(int x,int y){
++ecnt;
e[ecnt].nxt = head[x];
e[ecnt].w = 1;
e[ecnt].to = y;
head[x] = ecnt;
}//存图
int n,m;
int vis[3000001] = { };
int s = 1;
int ans[1000001] = { };
priority_queue<pr, vector<pr>, greater<pr> > q;//优先队列的优化版哥斯拉,要不然过不了
void dijkstra()
{
memset(dis, 0x3f,sizeof(dis));
dis[1] = 0;
ans[1] = 1;
q.push(make_pair(0, s));
while(!q.empty())
{
int now = q.top().second; //now是目前的点
int d = q.top().first;//d是距离
q.pop();
if(d != dis[now]) continue;
for(int i = head[now]; ~i; i = e[i].nxt)
{
int v = e[i].to;
if(d + e[i].w == dis[v]){
ans[v] = (ans[now] + ans[v]) % 100003;
}//能走到,最短,计算
if(dis[v] > dis[now] + e[i].w)
{
dis[v] = dis[now] + e[i].w;
ans[v] = ans[now];
q.push(make_pair(dis[v], v));
}
}
}
}
int main(){
scanf("%d %d" ,&n,&m);
memset(head,-1,sizeof(head));
for(int i = 1;i <= m; i++){
int a,b;
scanf("%d %d" ,&a,&b);
if(a == b) continue; //自环没用
addEdge(a,b);//无向图,正着存一遍反着存一遍
addEdge(b,a);
}
dijkstra();
for(int i = 1;i <= n; i++){
printf("%d\n" ,ans[i]);
}
return 0;
}
说实话这个写出来确实不好理解,但是bfs确实还不会,以下是从题解找的一个bfs供参考理解:
因为所有的边权都为1,所以一个点的最短路就相当于是它在BFS搜索树中的深度。一个点最短路一定经过了一个层数比它少一的结点(否则不是最短路)。所以用每个相邻且层数比当前结点层数少一的点更新当前点的路径跳数即可。
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<queue>
using namespace std;
const int maxn=1000000+1,maxm=2000000+1,INF=0x7f7f7f7f,MOD=100003;
vector<int>G[maxn];int dep[maxn];bool vis[maxn];int cnt[maxn];
int main(){
int N,M;scanf("%d%d",&N,&M);
for(int i=1;i<=M;i++){
int x,y;scanf("%d%d",&x,&y);
G[x].push_back(y);
G[y].push_back(x);
}
queue<int>Q;dep[1]=0;vis[1]=1;Q.push(1);cnt[1]=1;
while(!Q.empty()){
int x=Q.front();Q.pop();
for(int i=0;i<G[x].size();i++){
int t=G[x][i];
if(!vis[t]){vis[t]=1;dep[t]=dep[x]+1;Q.push(t);}
if(dep[t]==dep[x]+1){cnt[t]=(cnt[t]+cnt[x])%MOD;}
}
}
for(int i=1;i<=N;i++){
printf("%d\n",cnt[i]);
}
return 0;
}
3、多年以后,笨笨长大了,成为了电话线布置师。由于地震使得某市的电话线全部损坏,笨笨是负责接到震中市的负责人。该市周围分布着N(1<=N<=1000)根据1……n顺序编号的废弃的电话线杆,任意两根线杆之间没有电话线连接,一共有p(1<=p<=10000)对电话杆可以拉电话线。其他的由于地震使得无法连接。
第i对电线杆的两个端点分别是ai,bi,它们的距离为li(1<=li<=1000000)。数据中每对(ai,bi)只出现一次。编号为1的电话杆已经接入了全国的电话网络,整个市的电话线全都连到了编号N的电话线杆上。也就是说,笨笨的任务仅仅是找一条将1号和N号电线杆连起来的路径,其余的电话杆并不一定要连入电话网络。
电信公司决定支援灾区免费为此市连接k对由笨笨指定的电话线杆,对于此外的那些电话线,需要为它们付费,总费用决定于其中最长的电话线的长度(每根电话线仅连接一对电话线杆)。如果需要连接的电话线杆不超过k对,那么支出为0.
请你计算一下,将电话线引导震中市最少需要在电话线上花多少钱?
这道题是一个二分,很有思维含量啊,我想了好久才想到,但是这个代码弄起来也不容易,现在也没写出来我自己的代码
思路就是你要二分线段长,把比mid大的都赋值成1,比它小的都是0,然后再用最短路求,如果从1到n最后小于等于k,这个答案就是合法的,否则就是不合法的。这个题我现在代码也没出来,所以先控一下。
QAQ
然后hwz大佬又额外学习了分层最短路,接下来是他的代码
(同时也转载了他分层最短路的博客)
洛谷P4568飞行路线
题目描述
Alice和Bob现在要乘飞机旅行,他们选择了一家相对便宜的航空公司。该航空公司一共在n个城市设有业务,设这些城市分别标记为0到n-1,一共有m种航线,每种航线连接两个城市,并且航线有一定的价格。
Alice和Bob现在要从一个城市沿着航线到达另一个城市,途中可以进行转机。航空公司对他们这次旅行也推出优惠,他们可以免费在最多k种航线上搭乘飞机。那么Alice和Bob这次出行最少花费多少?
输入描述:
数据的第一行有三个整数,n,m,k,分别表示城市数,航线数和免费乘坐次数。
第二行有两个整数,s,t,分别表示他们出行的起点城市编号和终点城市编号。(0 ≤ s,t < n)
接下来有m行,每行三个整数,a,b,c,表示存在一种航线,能从城市a到达城市b,或从城市b到达城市a,价格为c。(0 ≤ a,b < n,a与b不相等,0 ≤ c ≤ 1000)
对于30%的数据,2<=n<=50,1<=m<=300,k=0;
对于50%的数据,2<=n<=600,1<=m<=6000,0<=k<=1;
对于100%的数据,2<=n<=10000,1<=m<=50000,0<=k<=10.
输出描述:
只有一行,包含一个整数,为最少花费。
思路:可以建k+1层的分层图,分层图可以理解成互相平行的图,每层内的图都是题目输入的那个图,相邻层之间的边权值都赋0,不相邻的层之间没有路,所以到达第k+1层时就走了k条权值为0的边(边和点的数量要算好),然后跑地杰斯特拉就好了。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N=2100010;
int n,m,k,s,t,dist[N];
int h[N],e[N],ne[N],w[N],idx;
void add(int a,int b,int c)
{
e[idx]=b;
w[idx]=c;
ne[idx]=h[a];
h[a]=idx++;
}
bool st[N];
void dij()
{
priority_queue<pii,vector<pii>,greater<pii> > q;
q.push({0,s});
dist[s]=0;
while(q.size())
{
int u=q.top().second;
q.pop();
if(st[u]) continue;
st[u]=1;
for(int i=h[u];i!=-1;i=ne[i])
{
int v=e[i];
if(!st[v]&&dist[v]>dist[u]+w[i])
{
dist[v]=dist[u]+w[i];
q.push({dist[v],v});
}
}
}
}
int main()
{
memset(h,-1,sizeof h);
memset(dist,0x3f,sizeof dist);
scanf("%d%d%d%d%d",&n,&m,&k,&s,&t);
for(int i=1;i<=m;i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
for(int j=0;j<=k;j++)
{
add(a+j*n,b+j*n,c);//层内的边
add(b+j*n,a+j*n,c);
if(j!=k)//第j层与下一层的边
{
add(a+j*n,b+(j+1)*n,0);
add(b+j*n,a+(j+1)*n,0);
}
}
}
dij();
int ans=0x3f3f3f3f;
for(int i=0;i<=k;i++)
ans=min(dist[t+i*n],ans);
cout<<ans;
return 0;
}
洛谷P1073 最优贸易
C国有n个大城市和m条道路,每条道路连接这n个城市中的某两个城市。任意两个城市之间最多只有一条道路直接相连。这m条道路中有一部分为单向通行的道路,一部分为双向通行的道路,双向通行的道路在统计条数时也计为1条。
C国幅员辽阔,各地的资源分布情况各不相同,这就导致了同一种商品在不同城市的价格不一定相同。但是,同一种商品在同一个城市的买入价和卖出价始终是相同的。
商人阿龙来到C国旅游。当他得知同一种商品在不同城市的价格可能会不同这一信息之后,便决定在旅游的同时,利用商品在不同城市中的差价赚回一点旅费。设C国n个城市的标号从1-n,阿龙决定从1号城市出发,并最终在n号城市结束自己的旅行。在旅游的过程中,任何城市可以重复经过多次,但不要求经过所有n个城市。阿龙通过这样的贸易方式赚取旅费:他会选择一个经过的城市迈入他最喜欢的商品——水晶球,并在之后经过的另一个城市卖出这个水晶球。用赚取的差价当作旅费。由于阿龙主要是来C国旅游,他决定这个贸易只进行最多一次。当然,在赚不到差价的情况下它就无需进行贸易。
假设C国有5个大城市,城市的编号和道路连接情况如下图,单向箭头表示这条道路为单向通行。双向箭头表示这条道路为双向通行。
假设1~n号城市的水晶球价格分别为4,3,5,6,1。
阿龙可以选择如下一条线路:1->2->3->5,并在2号城市以3的价格买入水晶球,在3号城市以5的价格卖出水晶球,赚取的旅费数为2。
阿龙也可以选择如下一条线路:1->4->5->4->5,并在第1次到达5号城市时以1的价格买入水晶球,在第2次到达4号城市时以6的价格卖出水晶球,赚取的旅费数为5。
现在给出n个城市的水晶球价格,m条道路的信息(每条道路所连接的两个城市的编号以及该条道路的通行情况)。请你告诉阿龙,他最多能赚钱多少旅费。
输入描述:
第一行包含2个正整数n和m,中间用一个空格隔开,分别表示城市的数目和道路的数目。
第二行n个正整数,每两个正整数之间用一个空格隔开,按标号顺序分别表示这n个城市的商品价格。
接下来m行,每行有3个正整数,x,y,z,每两个整数之间用一个空格隔开。如果z=1,表示这条道路是城市x到城市y之间的单向道路;如果z=2,表示这条道路为城市x和城市y之间的双向道路。
输出描述:
共1行,包含1个整数,表示最多能赚取的旅费。如果没有进行贸易,则输出0。
分层图还可以表示不同状态:第一层表示没有买入卖出,第二层表示买入一个,第三层表示卖出一个。那么层内边权都是0,第1层的u到第二层的v有边的话边权为u点价格的负值,第2层的u到第3层的v有边的话边权为u点的价格。
最终的结果就在第3层的n点。
#include<bits/stdc++.h>
using namespace std;
const int N=300010,M=3000010;
int n,m,d[N],h[N],e[M],ne[M],w[M],idx;
void add(int a,int b,int c)
{
w[idx]=c;
e[idx]=b;
ne[idx]=h[a];
h[a]=idx++;
}
int dist[N];
bool st[N];
void spfa()
{
for(int i=1;i<=n;i++) dist[i]=dist[i+n]=dist[i+2*n]=-1e5;
queue<int> q;
dist[1]=0;
q.push(1);
st[1]=1;
while(q.size())
{
int u=q.front();
q.pop();
st[u]=0;
for(int i=h[u];i!=-1;i=ne[i])
{
int v=e[i];
if(dist[v]<dist[u]+w[i])
{
dist[v]=dist[u]+w[i];
if(!st[v])
{
q.push(v);
st[v]=1;
}
}
}
}
}
int main()
{
memset(h,-1,sizeof h);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",d+i);
while(m--)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,0);
add(a+n,b+n,0);
add(a+2*n,b+2*n,0);
add(a,b+n,-d[a]);
add(a+n,b+2*n,d[a]);
if(c==2)
{
add(b,a,0);
add(b+n,a+n,0);
add(b+2*n,a+2*n,0);
add(b,a+n,-d[b]);
add(b+n,a+2*n,d[b]);
}
}
spfa();
int ans=0;
cout<<max(0,dist[3*n]);
}
接下来是这道题的分层最短路:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N=2100010;
int n,m,k,s,t,dist[N];
int h[N],e[N],ne[N],w[N],idx;
struct node
{
int x,d;
friend bool operator<(node x,node y)
{
return x.d>y.d;
}
};
void add(int a,int b,int c)
{
e[idx]=b;
w[idx]=c;
ne[idx]=h[a];
h[a]=idx++;
}
bool st[N];
priority_queue<node>q;
void dij()
{
q.push(node{s,0});
dist[s]=0;
while(q.size())
{
int u=q.top().x;
q.pop();
if(st[u]) continue;
st[u]=1;
for(int i=h[u];i;i=ne[i])
{
int v=e[i];
if(dist[v]>max(dist[u],w[i]))
{
dist[v]=max(dist[u],w[i]);
q.push(node{v,dist[v]});
}
}
}
}
int main()
{
memset(h,-1,sizeof h);
memset(dist,0x3f,sizeof dist);
scanf("%d%d%d",&n,&m,&k);
s=1;
t=n;
for(int i=1;i<=m;i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
for(int j=0;j<=k;j++)
{
add(a+j*n,b+j*n,c);
add(b+j*n,a+j*n,c);
add(a+j*n,b+(j+1)*n,0);
add(b+j*n,a+(j+1)*n,0);
}
}
dij();
if(dist[k*n+t]!=0x3f3f3f3f)
{
printf("%d\n",dist[k*n+t]);
}
else
{
printf("-1\n");
}
return 0;
}
4、灾后重建
给出B地区的村庄数N,村庄编号从0到N−1,和所有M条公路的长度,公路是双向的。并给出第i个村庄重建完成的时间ti ,你可以认为是同时开始重建并在第ti 天重建完成,并且在当天即可通车。若t i 为0则说明地震未对此地区造成损坏,一开始就可以通车。之后有Q个询问(x,y,t),对于每个询问你要回答在第t天,从村庄x到村庄y的最短路径长度为多少。如果无法找到从x村庄到y村庄的路径,经过若干个已重建完成的村庄,或者村庄x或村庄y在第t天仍未重建完成 ,则需要返回−1。
这道题就是一个基础的floyd模版题了
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
int ti[100001] = { };
int n,m,q;
struct Edge{
int nxt,to,w;
}e[1000001];
int vis[1000001] = { };
bool flag = 0;
int dp[300][300][300] = { };
int dis[1000001] = { };
int f[10001][100001] = { };
int h = 1;
void update(int x){
for(int i = 0;i < n; i++){
for(int j = 0;j < n; j++){
if(f[i][j] > f[i][x] + f[j][x]){
f[i][j] = f[j][i] = f[i][x] + f[j][x];
}
}
}
return;
}
int main(){
scanf("%d %d" ,&n,&m);
for(int i = 0;i < n; i++){
scanf("%d" ,&ti[i]);
}
for(int i = 0;i < n; i++){
for(int j = 0;j < n; j++){
f[i][j] = 1e9;
}
}
for(int i = 0;i < n; i++) f[i][i] = 0;
int a,b,c;
for(int i = 1;i <= m; i++){
scanf("%d %d %d" ,&a,&b,&c);
f[a][b] = f[b][a] = c;
}
scanf("%d" ,&q);
int now = 0;
for(int i = 1;i <= q; i++){
scanf("%d %d %d" ,&a,&b,&c);
while(ti[now] <= c && now < n){
update(now);
now++;
}
if(ti[a] > c || ti[b] > c){
printf("-1\n");
}
else{
if(f[a][b] == 1e9) printf("-1\n");
else printf("%d\n" ,f[a][b]);
}
}
return 0;
}
bfs!!!dp!!!有时间就要抓紧学会!不难发现我对图论这一块的理解还是有些吃力的,在不理解的前提下还是先把代码记住然后多刷题来帮助理解吧,分层图挺管用的,到时候额外做一期分层图的,好好学习一下
526

被折叠的 条评论
为什么被折叠?



