最短路相关技术

板子是一定要记的,但不够,全是思维题,要解放思想开动脑筋。

板子

Floyd

是全源最短路。

只要最短路存在(无负环),不管有向无向,边权正负,都可以用。

板子
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]);
      \\dis[k][i][j]=min(dis[k-1][i][j],dis[k-1][i][k]+dis[k-1][k][j]);
  }
}

本质是一个DP。

O(n3)

Dijkstra

是单源最短路。

只适用于非负权图。

  • 暴力做O(n2)(适合稠密图)

  • 线段树/二叉堆优化O(mlogn)(适合稀疏图)

  • 堆优化O(mlogm)(适合稀疏图)

  • 边权只有01时,把堆优化中的堆换成一个deque,每次松弛边权为0的边就把点放在前面,否则把点放在后面。O(n+m),相当于一个BFS。

堆优化板子(最好写的一个)
priority_queue< pair<int,int> > q;//默认的是大根堆,所以下方dis[v]取相反数。
scanf("%d%d",&s,&t);
for(int i=0;i<=n;++i) dis[i]=inf;
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=e[i].nxt){
		int v=e[i].v,w=e[i].w;
		if(dis[v]>dis[u]+w){
			dis[v]=dis[u]+w;
			q.push(make_pair(-dis[v],v));
		}
	}
}
暴力板子
for(int u=1;u<=n;u++){
	dis[u]=inf;
	vis[u]=false;
}
dis[s]=0;
for(int i=1;i<=n;i++){
	int minx=inf,u=0;
	for(int v=1;v<= n;v ++){
		if(vis[v]||dis[v]>minx) continue;
		minx=dis[v],u=v;
	}
	vis[u]=1;
	for(int j=head[u];j;j=e[j].nxt){
		int v=e[j].v,w=e[j].w;
		if(dis[v]<dis[u]+w) continue;
		dis[v]=dis[u]+w;
	}
}
线段树优化板子
#include<bits/stdc++.h>

using namespace std;

const int maxn=1e5+10,maxm=2e5+10,inf=1e9+7;
int n,m,tot,s,head[maxn],dis[maxn],mi[maxn<<2];
struct edge{
	int v,w,nxt;
}e[maxm];

void add(int u,int v,int w){
	e[++tot].v=v;
	e[tot].w=w;
	e[tot].nxt=head[u];
	head[u]=tot;
}

#define ls(k) k<<1
#define rs(k) k<<1|1

void pushup(int k){
	mi[k]=min(mi[ls(k)],mi[rs(k)]);
}

void build(int k,int l,int r){
	mi[k]=inf;
	if(l==r) return;
	int mid=l+r>>1;
	build(ls(k),l,mid);
	build(rs(k),mid+1,r);
	pushup(k);
}

void upd(int k,int l,int r,int p,int v){
	if(l==r){
		mi[k]=v;
		return;
	}
	int mid=l+r>>1;
	if(p<=mid) upd(ls(k),l,mid,p,v);
	else upd(rs(k),mid+1,r,p,v);
	pushup(k);
}

int query(int k,int l,int r){
	if(l==r) return l;
	int mid=l+r>>1;
	if(mi[ls(k)]<=mi[rs(k)]) return query(ls(k),l,mid);
	else return query(rs(k),mid+1,r);
}

\\其实就是用线段树做优先队列在做的事情。
\\线段树维护的是还未确定最短路的集合,inf即可以表示已经被移出了该集合,也可以表示最开始初始化的最短路长度。
\\每次移出该集合中最短路最小的点,并用这个点的出边进行松弛操作。

void dijkstra(){
	for(int i=1;i<=n;++i) dis[i]=inf;
	build(1,1,n);
	upd(1,1,n,s,0);
	dis[s]=0;
	while(mi[1]^inf){
		int u=query(1,1,n);
		upd(1,1,n,u,inf);
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].v,w=e[i].w;
			if(dis[v]>dis[u]+w){
				dis[v]=dis[u]+w;
				upd(1,1,n,v,dis[v]);
			}
		}
	}
}

int main(){
	scanf("%d%d%d",&n,&m,&s);
	for(int i=1;i<=m;++i){
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		add(u,v,w);
	}
	dijkstra();
	for(int i=1;i<=n;++i) printf("%d ",dis[i]);
	return 0;
} 

Bellman-Ford和SPFA

这两个都可以处理带负权的图上的最短路,并且可以判断最短路是否存在(判断负环)

SPFA是Bellman-Ford的队列优化。

Bellman-Ford是在每轮循环中对每条边尝试松弛,每轮松弛至少使最短路长度+1,所以最多进行n1轮。O(nm)

显然只有在上一次被松弛过的点所连的边才可能引起下次松弛,用队列存一下,每次只松弛必要的边,就得到了SPFA。

SPFA大多数时候跑得比较快,但最坏O(nm),要小心使用尽量别用。SPFA容易被卡,但费用流要用

判断负环:记录一下最短路经过了几条边,当最短路至少经过n条边时,就说明可以抵达负环(因为如果没有负环,最短路最多经过n1条边)。

注意这里只能判断从起点s出发能否抵达负环。若图不保证连通,要判断是否存在负环,则要建立一个超级源点S,从S向每个点连边(边权为0),再跑SPFA。

SPFA判断负环板子
bool spfa(int n, int s){
  for(int i=1;i<=n;++i) dis[i]=inf;
  dis[s]=0,inque[s]=1;
  q.push(s);
  while(!q.empty()){
    int u=q.front();
    q.pop();
    inque[u]=0;
    for(int i=head[u];i;i=e[i].nxt){
      int v=e[i].v, w=e[i].w;
      if(dis[v]>dis[u]+w){
        dis[v]=dis[u]+w;
        cnt[v]=cnt[u]+1; 
        if(cnt[v]>=n) return false;
        if(!inque[v]) q.push(v), inque[v]=1;
      }
    }
  }
  return true;
}

Johnson

能处理无负环图上的全源最短路。

流程:

  1. 建立超级源点S向每个点连边,边权为0.
  2. S为起点跑一遍SPFA,求出到每个点i的最短路hi
  3. 对于一条起点为u,终点为v,边权为w的边,重新设置边权为w+huhv
  4. 这样就保证了边权非负,以每个点为起点跑n轮Dijkstra即可。
  5. 最终求出uv的最短路为disu,v,则原来uv的最短路为disu,v+hvhu。若disu,v=inf,则u不可到达v

当Dijkstra使用堆优化时,Johnson的时间复杂度为O(nmlogm),在稀疏图上比Floyd好。

Johnson全源最短路模板题

板子
#include<bits/stdc++.h>

using namespace std;
typedef long long ll;

const int maxn=3e3+10,maxm=6e3+10,inf=1e9;
int n,m,tot=0,head[maxn],cnt[maxn];
bool vis[maxn];
ll ans=0,dis[maxn],h[maxn];
struct edge{
	int u,v,w,nxt;
}e[maxm+maxn];

void add(int u,int v,int w){
	e[++tot].u=u;
	e[tot].v=v;
	e[tot].w=w;
	e[tot].nxt=head[u];
	head[u]=tot;
}

void spfa(){
	memset(h,0x3f3f3f3f,sizeof h);
	queue<int> q;
	h[0]=0,vis[0]=true;
	q.push(0);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		vis[u]=false;
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].v,w=e[i].w;
			if(h[v]>h[u]+w){
				h[v]=h[u]+w;
				cnt[v]=cnt[u]+1;
				if(cnt[v]>n){
					puts("-1");
					exit(0);
				}
				if(!vis[v]){
					q.push(v);
					vis[v]=true;
				}
			}
		}
	}
}

void dijkstra(int s){
	for(int i=1;i<=n;++i) vis[i]=false,dis[i]=inf;
	dis[s]=0;
	priority_queue< pair<ll,int> > q;
	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=e[i].nxt){
			int v=e[i].v,w=e[i].w;
			if(dis[v]>dis[u]+w){
				dis[v]=dis[u]+w;
				q.push(make_pair(-dis[v],v));
			}
		}
	}
}

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;++i){
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		add(u,v,w);
	}
	for(int i=1;i<=n;++i) add(0,i,0);
	spfa();
	for(int i=1;i<=tot;++i){
		int u=e[i].u,v=e[i].v;
		e[i].w+=h[u]-h[v];
	}
	for(int i=1;i<=n;++i){
		dijkstra(i);
		ans=0;
		for(int j=1;j<=n;++j){
			if(dis[j]==inf) ans+=1ll*j*inf;
			else ans+=1ll*j*(dis[j]+h[j]-h[i]);
		}
		printf("%lld\n",ans);
	}
	return 0;
} 

各种技术

同余最短路

OI wiki上说,可以通过同余的性质来构造一些状态,来减小空间复杂度,这些状态也对应着图中的点,转移通常形如f(i+y)=f(i)+y

问题的常见类型:

  1. 给出n个整数,求能拼出多少种其他的不同数字(可以重复取)。

  2. 给出n个整数,求拼不出来的最小/最大的数。

  3. 给出n个整数,求至少取几个数能拼出模kp的数。

这种建模也很常见,哪怕没有取模操作。形式化的,会出现形如aixi的东西,然后求一些东西。

通常会见到一个很吓人的值域,然而在模意义下值域其实很小。选取最小的数可以使得值域最小。

最短路的状态disi可能被修改为在模x(选取的那个数)的意义下等于i的XX信息。

P3403 跳楼机

posted @   RandomShuffle  阅读(4)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示