【分层最短路】dijkstra讲解+复习 /2018 ACM-ICPC南京网络赛 Magical Girl Haze

首先,HDU-2544 来回顾一下dij这个东西(链接: 这里

讲解: (来源:坐在马桶上学算法) 不断对边进行松弛

(以下为copy)

算法的基本思想是:每次找到离源点(上面例子的源点就是 1 号顶点)最近的一个顶点,然后以该顶点为中心进行扩展,最终得到源点到其余所有点的最短路径。基本步骤如下:

  • 将所有的顶点分为两部分:已知最短路程的顶点集合 P 和未知最短路径的顶点集合 Q。最开始,已知最短路径的顶点集合 P 中只有源点一个顶点。我们这里用一个 book[ i ]数组来记录哪些点在集合 P 中。例如对于某个顶点 i,如果 book[ i ]为 1 则表示这个顶点在集合 P 中,如果 book[ i ]为 0 则表示这个顶点在集合 Q 中。
  • 设置源点 s 到自己的最短路径为 0 即 dis=0。若存在源点有能直接到达的顶点 i,则把 dis[ i ]设为 e[s][ i ]。同时把所有其它(源点不能直接到达的)顶点的最短路径为设为 ∞。
  • 在集合 Q 的所有顶点中选择一个离源点 s 最近的顶点 u(即 dis[u]最小)加入到集合 P。并考察所有以点 u 为起点的边,对每一条边进行松弛操作。例如存在一条从 u 到 v 的边,那么可以通过将边 u->v 添加到尾部来拓展一条从 s 到 v 的路径,这条路径的长度是 dis[u]+e[u][v]。如果这个值比目前已知的 dis[v]的值要小,我们可以用新值来替代当前 dis[v]中的值。
  • 重复第 3 步,如果集合 Q 为空,算法结束。最终 dis 数组中的值就是源点到所有顶点的最短路径。

(copy结束)如上图, 全部松弛完成之后的是这样的↑

差不多是从起点开始跑,到终点、跑完之后结束。

中间的过程,每次在可选择的点里面选一个没有跑过,并且距离最近的。

一旦被选定,这个点用过了,就要标记,把这个点的出度全部访问一遍,在这个过程中如果有未曾访问过的满足了可更新的条件,就去更新,把这条边的出度更新完为止。

【对于无向边,要加个2次,一样的!】呱呱呱,这就是算法的美妙之处吧1!!!

更新的是后面的点。每次选定的时候选剩下的最优的,从1-n即可。(所以说上面写的真好。。。)

然后过程中呢,

每次都存一下,然后最后跑完

【简单实现一下下】

代码:  (18.4.7 ac one)

#include<vector>
#include<iostream>
#include<queue>
using namespace std;
struct node {
	int next;
	int dis;
	bool operator< (const node &p)const {
		return dis > p.dis;
	}
	node(int _next=0,int _dis=0):next(_next),dis(_dis){}
}h[105];
vector<node>e[105];//记录边的地方....
priority_queue<node> q;
bool vis[10005];//边
int dist[10005];
const int INF = 0x3f3f3f3f;
void dij(int x, int n) {
    memset(vis, false, sizeof(vis));
	for (int i = 1; i <= n; i++) dist[i] = INF;
	while (!q.empty()) q.pop();
	dist[x] = 0;//...自己到自己是0  没错吧
	q.push(node(x, 0));//吧自己也加进去..没错吧
	while (!q.empty()){
		node temp = q.top(); q.pop();
		int u = temp.next;
        if (vis[u]) continue;
		vis[u] = true;
		for (int i = 0; i < e[u].size(); i++){
			int v = e[temp.next][i].next;
			int cost = e[u][i].dis;
			if (!vis[v] && dist[v] > dist[u] + cost){
				dist[v] = dist[u] + cost;
				q.push(node(v, dist[v]));
			}
		}
	}
}
int main() {
	int n, m; while (cin >> n >> m) {
		if (m == 0 && n == 0)break;
		int a1,a2,v;
		for (int i = 0; i < n; i++)e[i].clear();
		for (int i = 0; i < m; i++){
			cin >> a1 >> a2 >> v;
			e[a1].push_back(node(a2, v));//出度和值..
			e[a2].push_back(node(a1, v));
		}
		dij(1, n);
		long long ans = dist[n];
		cout << ans << endl;
	}
	return 0;
}

【写后反思】dij这个东西,写的再熟练也不为过吧。啊…… 再熟练也不为过……

q如果没有满一样要清理,如果是循环输入的,把所有遍历到的东西都初始化一遍吧……

1.边可以存,但是怎么存?node里不是只有nxt和v吗?怎么保存当前的顶点嗯?

怎么初始化?不要忽略记录答案的dist[i] 

q.push( x,0 ) ;   注意这里是push ,在队列里 而不是push_back

node temp = q.top() ;q.pop() ;     比如1可以到2和3,那么先遍历1、 1的v是0

【代码实现时间到】遍历1在g[1]里面的所有条件,其中已经确定住的不去遍历,没被确定的能更新就更新

(待续)

注意:vector<node> g 

还有注意排序的时候的序号

2.要对整个过程熟悉,比如为什么要vis,为什么要inf,那是因为初始都是不可达,每次搞完一个就确定了,vis在比较的时候已经遍历了就不用管了

3.优先队列里不能写bool   cmp ,那个是用在sort里面的

这么写:

bool  operator < (node &p) {

return dis>p.dis;}

bool operator< (const node &p)const {

return dis > p.dis;

}

因为默认的优先队列里面,是优先级大的先出来
比如一个序列同样是12345
优先队列出来的顺序就会变成54321
因为是大的先出来
所以你要把里面的排序变成54321,它出来的才是12345 所以上面写的dis>p.dis
这的确是从小到大安排的。。  dis>p.dis

 

那么对于这个题... 有两种方法

for(int i = head[x]; i; i = nxt[i]){//开始松弛能到达的点
            int u = to[i];//分两种情况
            if(dis[x][lev] + val[i] < dis[u][lev]){//在不使任何新的边为0的情况下的松弛,相当于普通dijk
                dis[u][lev] = dis[x][lev] + val[i];
                if(!vis[u][lev]) q.push(Node(dis[u][lev],u,lev));//如果这个点这种状态还没当过出发点松弛就入队
            }
            if(lev + 1 <= k && dis[x][lev] < dis[u][lev+1]){//如果是这条边为0,再进行松弛,然后同上判断是否入队
                dis[u][lev+1] = dis[x][lev];
                if(!vis[u][lev+1]) q.push(Node(dis[u][lev+1],u,lev+1));
            }
        }

还有是分层读入

for (int i=1;i<=m;i++)
		{
			scanf("%d%d%d",&u,&v,&c);
			u--;v--;
                        //分层
			for (int j=0;j<=k;j++)
			{
				G[u*(k+1)+j].push_back(node(v*(k+1)+j,c));
				if (j<k)
				G[u*(k+1)+j].push_back(node(v*(k+1)+j+1,0));
			}
		}

都还没写

来源: 1   2

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值