最短路

最短路

其实最短路来自图论 , 所以先不讲最短路 , 先来讲讲关于图的基本知识

存图

链式前向星

好简单直接过

但是!!!如果是无向边数组记得开双倍空间呀!

放个代码:

struct egde{
	int x, y, z, next;
}e[maxm<<1];//无向图开双倍空间

int head[maxn], vis[maxn], dis[maxn], cnt;

inline void add_edge( int x , int y , int z ){
	e[++cnt].x = x , e[cnt].y = y , e[cnt].z = z;
	e[cnt].next = head[x];
	head[x] = cnt;
}

for(int i = head[now]; i; i = e[i].next){
    /*do something*/
}

我个人比较喜欢用vector存图

邻接矩阵

这个更暴力了,所以不讲。这里讲一个队邻接矩阵的优化:

用vector实现邻接矩阵(其实这个时候已经叫领接表了)STL大法好

代码:

vector<pair<int, int> > v[maxn];

void add_edge(int x, int y, int z){
	return (void)(v[x].push_back(make_pair(y,z)), v[y].push_back(make_pair(x,z)));
} 

for(int i = 0; i < v[now].size(); i ++){
	int to = v[now][i].first, len = v[now][i].second;
	/*do someting*/
}

多好

图的遍历

基于栈的DFS

象征性放一下代

void dfs(int now){
    vis[now] = 1;
    for(int i = 0; i < v[now].size(); i ++){
        int to = v[now][i];
        if(!vis[to]) dfs(to);
    }
    return;
}

基于队列的BFS

void bfs(int x){
	queue<int> q;
    q.push(x), vis[x] = 1;
    while(q.size()){
	int now = q.front();
        q.pop();
        for(int i = 0; i < v[now].size(); i ++){
	    int to = v[now][i];
            if(!vis[to]) q.push(to), vis[now] = 1;
        }
    }
    return;
} 

最短路算法

SPFA

用于求解单源最短路问题,即求-一个点到图上其它点的最短路。

\(Bellman-Ford\)的优化。

算法描述:

  1. 对每个点\(x\)设置一个变量\(dis[x]\), 表示它离起点8的最短距离,令\(dis[x]= 0\)
  2. 维护一个队列,先将起点入队。
  3. 每次从队列中取出一个点,用它去更新所有相邻的点。
  4. \(dis[to] = min(dis[to],~dis[now] + len)\)
  5. 在一个点被更新后,如果它不在队列中,则将它入队。

时间复杂度上界仍然为\(O(nm)\),但对于随机数据,该算法效率非常高。

说人话:SPFA非常非常非常容易被卡qwq。

代码:

void SPFA(int x){
    queue<int> q;
    q.push(x), vis[x] = 1, dis[x] = 0;
    while(q.size()){
        int now = q.front();
        q.pop(), vis[now] = 0;
        for(int i = 0; i < v[now].size(); i ++){
            int to = v[now][i].first, len = v[now][i].second;
            if(dis[to] > dis[now] + len){
		dis[to] = dis[now] + len;
                if(!vis[to]){
		    q.push(to);
                    vis[to] = 1;
                }
            }
        }
    }
    return;
}

Dijkstra

用于求单源最短路问题

算法描述:

  1. 将点分为两类, 一类是最短路已确定的点,另一类是最短路未确定的点。
  2. 对每个点\(x\)设置一个变量\(dis[x]\),表示它离起点的最短距离。
  3. 首先将起点\(x\)标记为最短路已确定的点,即\(vis[x]=1\),并且\(dis[x]= 0\)
  4. 每次从所有最短路未确定的点中取出离起点最近的点,将它标记为最短路已经确定,用它去更新与它相邻的所有点。
  5. \(dis[to] = min(dist[to],~dis[now] + len)\)
  6. 时间复杂度为\(O(n^2)\)

堆优化:使用链式前向星或邻接矩阵存图,用堆来优化寻找最近点的过程,时间复杂度降至\(O(mlogn)\)

Dijkstra不会被卡,因为他的时间复杂度是严格的\(O(mlogn)\)

注意啦:因为Dijkstra的算法原理是贪心,而出现负边权的话我们显然可以发现贪心是错误的,所以Dijkstra无法解决带有负权值得最短路问题。

这里只提供\(O(mlogn)\)的写法。

代码:

void Dijkstra(){
	priority_queue<pair<int,int>, vector<pair<int,int> >, greater<pair<int,int> > > q;
	q.push(make_pair(0,1));
	dis[1] = 0;
	while(q.size()){
		int now = q.top().second, len = q.top().first;
		q.pop();
		if(vis[now]) continue;
		vis[now] = 1;
		for(int i = 0; i < v[now].size(); i ++){
			int to = v[now][i].first, weight = v[now][i].second;
			if(dis[to] > len + weight){
				dis[to] = len + weight;
				q.push_back(make_pair(dis[to],to));
			}
		}
	}
	return;
}

Floyd

挺简单的我也懒得打字了,听我口胡。

代码:

void Floyd(){
	for(int k = 1; k <= n; k++)
	      for(int i = 1; i <= n; i++)
		      for(int j = 1; j <= n; j++)
			      if(i != j && j != k && k != i && dis[i][j] > dis[i][k] + dis[k][j])
				      dis[i][j] = dis[i][k] + dis[k][j];
	return;
}

最短路径输出

没用的小知识qwq。

在更新最短路的时候用\(pre\)数组记录前驱即可。

代码:

void print(int n){
    if(!pre[n]) return;
    print(pre[n]);
    printf("%d", n);
}

最短路计数

模板

这个点的路径数可以由上一个点转移过来。

代码:

#include <bits/stdc++.h>
using namespace std;

const int maxn = 1e6 + 10, mod = 100003;

vector<int> v[maxn];
int n, m, dis[maxn], vis[maxn], sum[maxn];

void BFS(int x){
	queue<int> q;
	q.push(x), vis[x] = 1, dis[x] = 0;
	while(q.size()){
		int now = q.front();
		q.pop();
		for(int i = 0; i < v[now].size(); i ++){
			int to = v[now][i];
			if(dis[to] > dis[now] + 1){
				dis[to] = dis[now] + 1;
				sum[to] = sum[now];
				vis[to] = 1;
				q.push(to);
			}else if(vis[to] and dis[to] == dis[now] + 1){
				sum[to] += sum[now];
				sum[to] %= mod;
			}
		}
	}
}

signed main(){
	memset(dis, 0x3f, sizeof(dis));
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; i ++){
		sum[i] = 1;
	}
	for(int i = 1, x, y; i <= m; i ++){
		scanf("%d%d", &x, &y);
		v[x].push_back(y);
		v[y].push_back(x);
	}
	BFS(1);
	for(int i = 1; i <= n; i ++){
		if(vis[i]) printf("%d\n", sum[i]);
		else printf("0\n");
	}
	return 0;
}

分层最短路

分成最短路其实就是把一个图分层然后我们来跑一遍最短路。通俗易懂的语言==废话​

分层最短路的关键在于我们如何去给这个图分层。

为例。 是道裸题

我们会发现这道题很熟悉,所以仔细观察,我们还会发现\(k\)非常的小。

我们考虑如何给图分层,首先我们把原来的图原封不动的复制\(k\)遍(具体实现过程并不是这样的,这样讲方便理解)。

每一层之间我们以权值为\(0\)的边连接.....我也口胡不清楚,上图。

以样例为例(图来自luogu)

首先我们把\(n\)个点变为了\(n+n \times k\)个点,其中每\(i\)\(i+n-1\)为一层好吧。

层与层之间的连接:如果\(x\)这个点到\(y\)这个点在原图上有边,那我们便在\(x+n \times (i-1)\)\(y+n \times i\)上连一条边权为\(0\)的边表示使用免费次数,以此类推....

这样下来我们的图便分好层了。

分层的意义在于我从\(x\)点到\(y\)点的最短路径上可以保证使用的免费飞行次数$ \leq k$次,但肯定是使用的次数越多优的qwq。

有个小细节需要注意,我们分层最短路相当于把原来的图扩大了,所以我们的数组也要开大亿点点,差不多是到\(n+n \times k\),其实只要你不\(MLE\)你弄成\((n+n \times k) \times 10\)也没有问题,只要自己代码不会出锅就行╮(─▽─)╭

具体实现方式看代码:

#include <bits/stdc++.h>
using namespace std;

const int maxn = 1e6;

int n, m, k, posx, posy, dis[maxn], vis[maxn];

vector<pair<int,int> > v[maxn];

inline void add_edge(int x, int y, int z){ return (void)v[x].push_back(make_pair(y,z)); }

void Dijkstra(int x){
	memset(dis, 0x3f, sizeof(dis));
	priority_queue<pair<int,int>, vector<pair<int,int> >, greater<pair<int,int> > > q;
	q.push(make_pair(0,x));
	dis[x] = 0;
	while(q.size()){
		int now = q.top().second, len = q.top().first;
		q.pop();
		if(vis[now]) continue;
		vis[now] = 1;
		for(int i = 0; i < v[now].size(); i ++){
			int to = v[now][i].first, weight = v[now][i].second;
			if(dis[to] > weight + len){
				dis[to] = weight + len;
				q.push(make_pair(dis[to],to));
			}
		}
	}
	return;
}

signed main(){
	scanf("%d%d%d%d%d", &n, &m, &k, &posx, &posy);
	for(int x, y, z; m; m --){
		scanf("%d%d%d", &x, &y, &z);
		add_edge(x, y, z);
		add_edge(y, x, z);
		for(int j = 1; j <= k; j ++){
			add_edge(x+(j-1)*n, y+j*n, 0);
			add_edge(y+(j-1)*n, x+j*n, 0);
			add_edge(x+j*n, y+j*n, z);
			add_edge(y+j*n, x+j*n, z);
		}
	}
	for(int i = 1; i <= k; i ++) add_edge(posy+(i-1)*n, posy+i*n, 0);
	Dijkstra(posx);
	printf("%d", dis[posy+k*n]);
	return 0;
}

做亿点题

衡水某二中集训的考试题

某二中集训的考试题-2_00.png-171.5kB

Solution

二分答案。听我口胡。

代码:

#include <bits/stdc++.h>
using namespace std;

const int maxn = 1000+10;

vector<pair<int,int> >adj[maxn];
vector<pair<int,int> >temp_adj[maxn];

int dis[maxn], n, m, k;

inline void addEdge(int u,int v,int w){
	adj[u].push_back(make_pair(v,w));
	adj[v].push_back(make_pair(u,w));
	return;
}
bool check(int ans){
	for(int i = 1;i<=n;i++){
		temp_adj[i].clear();
	}
	for(int i = 1;i<=n;i++){
		for(int j = 0;j<adj[i].size();j++){
			if(adj[i][j].second <= ans){
				temp_adj[i].push_back(make_pair(adj[i][j].first,0));
			}else{
				temp_adj[i].push_back(make_pair(adj[i][j].first,1));
			}
		}
	}
	memset(dis,0x3f,sizeof(dis));
	deque<int>q;
	dis[1] = 0;
	q.push_back(1);
	while(q.size()){
		int f = q.front();
		q.pop_front();
		for(int i = 0;i<temp_adj[f].size();i++){
			int to = temp_adj[f][i].first,weight = temp_adj[f][i].second;
			if(dis[to] > dis[f] + weight){
				dis[to] = dis[f]+weight;
				if(weight == 0){
					q.push_front(to);
				}else{
					q.push_back(to);
				}
			}
		}
	}
	if(dis[n] > k) return false;
	else return true;
}

int main(){
	cin>>n>>m>>k;
	for(int i=0;i<m;i++){
		int u,v,w;
		cin>>u>>v>>w;
		addEdge(u,v,w);
	}
	int l = 0,r = 1000000;
	while(l<r){
		int mid = (l+r)/2;
		if(check(mid)) r = mid;
		else l = mid+1;
	}
	if(check(r)) cout<<r<<endl;
	else cout<<-1<<endl;
	return 0;
}

转化为图论模型

主要是看题。

P1854摆花

不是因为我不会动规才转化成最短路的。

Solution

听我口胡。

代码:

#include <bits/stdc++.h>
using namespace std;

const int maxn = 500;

queue<int> q;

int f, V, comp, ans, ansd, cnt, dis[maxn*maxn], vis[maxn*maxn], pre[maxn*maxn], head[maxn*maxn];
pair<int, int> temp[maxn][maxn];

struct edge{
	int to, length, next;
	inline void push(int x, int y, int z, int cnt){
		to = y, length = z, next = head[x];
		head[x] = cnt;
		return;
	}
	edge(){to = length = next = 0; return;}
}v[maxn*maxn];

inline void add_edge(int x, int y, int z){
	++ cnt;
	v[cnt].push(x,y,z,cnt);
	return;
}

void SPFA(int x){
	memset(pre, 0, sizeof(pre));
	memset(dis, 128, sizeof(dis));
	q.push(x);
	vis[x] = 1, dis[x] = 0;
	while(q.size()) {
	   int now = q.front();
	   q.pop(); 
	   vis[now] = 0;
	   for(int i = head[now]; i; i = v[i].next){
	   	int to = v[i].to, len = v[i].length;
	   	if(dis[to] < dis[now] + len){
	   		dis[to] = dis[now] + len;
	   		pre[to] = now;
	   		if(!vis[to]){
	   			q.push(to);
	   			vis[to] = 1;
	   		}
	      }
	   }
	}
	return ;
}
void print(int x, int high){
	if(!x) return;
	print(pre[x], high-1);
	printf("%d ", x - high*V);
}

signed main(){
	scanf("%d%d",&f,&V);
	for(int i = 1; i <= f; i ++)
		for(int j = 1; j <= V; j ++){
			scanf("%d",&temp[i][j].first);
			temp[i][j].second = ++comp;
		}
	for(int i = 1; i <= V-f+1; i ++) add_edge(0,temp[1][i].second,temp[1][i].first);
	for(int k = 1; k < f; k ++)
		for(int i = k; i <= V-f+k; i ++)
			for(int j = i + 1; j <= V-f+k+1; j ++)
				add_edge(temp[k][i].second,temp[k+1][j].second,temp[k+1][j].first);
	SPFA(0);
	for(int i = f*V-V+1; i <= comp; i ++)
		if(dis[i] > ans) ans = dis[i], ansd = i;
	printf("%d\n", ans);
	print(ansd,f-1);
	return 0;
}

AcWing177电路维修

Solution
建图:如果电线是 \ 就 从右上向左下连边,长为 1,从左上向右下连边,长为 0,意义是改变电线的方向代价是1,不改变就是0,之后跑最短路就行。

posted @ 2020-07-11 20:43  Vanyun  阅读(134)  评论(0编辑  收藏  举报