• 最短路


    最短路

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

    存图

    链式前向星

    好简单直接过

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

    放个代码:

    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
    ", sum[i]);
    		else printf("0
    ");
    	}
    	return 0;
    }
    

    分层最短路

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

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

    为例。 是道裸题

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

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

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

    以样例为例(图来自luogu)

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

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

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

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

    有个小细节需要注意,我们分层最短路相当于把原来的图扩大了,所以我们的数组也要开大亿点点,差不多是到(n+n imes k),其实只要你不(MLE)你弄成((n+n imes k) imes 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
    ", ans);
    	print(ansd,f-1);
    	return 0;
    }
    

    AcWing177电路维修

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

  • 相关阅读:
    ios学习-2015-7-9
    ios学习-2015-7-8
    ios学习-2015-7-7
    ios学习-2015-7-6
    ios学习-2015-7-5
    ios学习-2015-7-3
    ios学习-2015-7-2
    Selenium学习笔记之003:Web UI自动化知识点整理 分类: Selenium 2015-07-14 14:19 3人阅读 评论(0) 收藏
    Selenium学习笔记之002:IDE录制和回放 分类: Selenium 2015-07-12 17:13 19人阅读 评论(0) 收藏
    Selenium学习笔记之002:IDE录制和回放
  • 原文地址:https://www.cnblogs.com/Vanyun/p/13285392.html
Copyright © 2020-2023  润新知