最短路问题
\(Floyd\)
多源最短路算法,用邻接矩阵存储,可以处理负边权,不能处理负环,本质是个\(DP\)。
需要三层循环,时间复杂度为\(O(n^{3})\),在稠密图中有优势。
记得\(dis\)数组要初始化成正无穷(不相连的点之间),并且自己到自己的距离为\(0\),即\(dis[i][i]=0\)。
\(code\):
for(int k=1;k<=n;++k)
{
for(int i=1;i<=n;++i)
{
for(int j=1;j<=n;++j)
{
if(i==j||i==k||j==k) continue;
if(dis[i][j]>dis[i][k]+dis[k][j])
dis[i][j]=dis[i][k]+dis[k][j];
}
}
}
\(Dijkstra\)
原本时间复杂度为\(O(n^{2})\),用堆优化后,复杂度为\(O((n+m)log\ n)\),在稀疏图中有优势。
\(code\):
struct node
{
int val;
int num;
friend bool operator <(const node &x,const node &y)
{
return x.val>y.val;
}
};
priority_queue<node> q;
int dis[maxn];
bool vis[maxn];
void dijkstra()
{
for(int i=1;i<=n;++i) dis[i]=inf;
dis[s]=0;
q.push((node){0,s});
while(!q.empty())
{
node tmp=q.top();
q.pop();
int x=tmp.num;
if(vis[x]) continue;
vis[x]=true;
for(int i=head[x];i;i=e[i].nxt)
{
int y=e[i].to,v=e[i].v;
if(dis[y]>dis[x]+v)
{
dis[y]=dis[x]+v;
q.push((node){dis[y],y});
}
}
}
}
\(SPFA\)
为\(Bellmam-Ford\)算法加上队列优化,可以处理负边权,能判断负环。判负环跑最短路,判正环跑最长路。
时间复杂度最坏情况会退化为\(O(nm)\),会被卡,所以它死了。
如果图是随机生成的,时间复杂度为\(O(km)\)(\(k\)可以认为是个常数)。
\(SPFA\)的过程是\(bfs\),是在不断扩展节点。
用\(SPFA\)求最长路问题,考虑到\(SPFA\)处理负边权的特性,可以把边权取相反数,然后跑\(SPFA\),最后再取相反数输出就是最长路了。
正边权(数据相对较大)时,不要用\(SPFA\)。
\(code\):
int dis[maxn];
bool vis[maxn];
void spfa()
{
queue<int> q;
for(int i=1;i<=n;++i) dis[i]=inf;
q.push(s);
dis[s]=0;
vis[s]=true;
while(!q.empty())
{
int x=q.front();
q.pop();
vis[x]=false;
for(int i=head[x];i;i=e[i].nxt)
{
int y=e[i].to,v=e[i].v;
if(dis[y]>dis[x]+v)
{
dis[y]=dis[x]+v;
if(!vis[y])
{
vis[y]=true;
q.push(y);
}
}
}
}
}
其他
当图中存在两个状态时,如请柬有前往费用和返回费用,最优贸易有买入价和卖出价,这时不妨考虑再建一个反图,正向图反向图各跑一遍最短路。
有时不易发现用最短路,要将题意理清楚,推推式子,如大圣诞树(暑假欢乐赛\(T2\)),当时已经把式子推出来了,但傻傻的打了\(40\)分的暴力。
同余最短路,并不是用同余来跑最短路,而是通过同余来构造某些状态,从而达到优化时间空间复杂度的目的。如跳楼机。
分层图最短路,有一些动态规划的思想,建出多层的原图来跑最短路,如飞行路线和冻结
\(code\):
for(int i=1;i<=m;++i)
{
int a,b,v;
read(a),read(b),read(v);
add(a,b,v),add(b,a,v);
for(int j=1;j<=k;++j)
{
add(a+(j-1)*n,b+j*n,val);
add(b+(j-1)*n,a+j*n,val);
add(a+j*n,b+j*n,v);
add(b+j*n,a+j*n,v);
}
}
其中\(val\)为越层的花费
若\(val\)为\(0\),则需加上
\(code\):
for(int i=1;i<=k;++i) add(t+(i-1)*n,t+i*n,0);
来防止到终点的路径边数小于层数
若\(val\)不为\(0\),则需加上
\(code\):
for(int i=0;i<=k;++i) ans=min(ans,dis[n+i*n]);
来保证答案为最优解
最短路计数问题,问从\(s\)到\(t\)有几条最短路
单源最短路,在松弛时,若发现相等则路径条数更新,应用加法原理
\(code\):
void dijkstra()
{
for(int i=1;i<=n;++i) dis[i]=inf;
dis[1]=0;
tot[1]=1;
q.push((node){0,1});
while(!q.empty())
{
node tmp=q.top();
q.pop();
int x=tmp.num;
if(vis[x]) continue;
vis[x]=true;
for(int i=head[x];i;i=e[i].nxt)
{
int y=e[i].to,v=e[i].v;
if(dis[y]>dis[x]+v)
{
dis[y]=dis[x]+v;
tot[y]=tot[x];
q.push((node){dis[y],y});
}
else if(dis[y]==dis[x]+v)
tot[y]=tot[x]+tot[y];//更新路径条数
}
}
}
多源最短路,同理,在松弛时,若发现相等则路径条数更新,应用乘法原理
\(code\):
for(int i=1;i<=m;++i)
{
int a,b,v;
scanf("%d%d%d",&a,&b,&v);
dis[a][b]=dis[b][a]=min(dis[a][b],v);
tot[a][b]=tot[b][a]=1;//初始化
}
for(int k=1;k<=n;++k)
{
for(int i=1;i<=n;++i)
{
if(k==i) continue;
for(int j=1;j<=n;++j)
{
if(k==j||i==j) continue;
if(dis[i][j]>dis[i][k]+dis[k][j])
{
dis[i][j]=dis[i][k]+dis[k][j];
tot[i][j]=tot[i][k]*tot[k][j];
}
else if(dis[i][j]==dis[i][k]+dis[k][j])//表明k在i到j的最短路上
tot[i][j]+=tot[i][k]*tot[k][j];//更新路径条数
}
}
}