dijkstra的stl实现(最近觉得挺方便的

dijkstra的stl实现(最近觉得挺方便的

stl可作为跳板

              --- Rujia liu
struct node {
  int dis, id;
  node(int dis = 0, int id = 0) : dis(dis), id(id) {}
  bool operator < (const node& xx) const {
    return dis > xx.dis;
  }
};

struct Edge{
	int x,y,val;
	Edge(int x = 0, int y = 0, int val = 0) : x(x), y(y), val(val) {}
};//弧 
//以下为vector版 
struct Dijkstra {
	int n,m;
	vector<Edge> edges;// 边列表
	vector<int> G[MAXN];// 每个节点出发的边的编号(从0开始
	bool vis[MAXN];
	int d[MAXN];//s 到各点距离 
	int p[MAXN];// 最短路上的一条边
	
	void init(int n) {
		this->n = n;
		for(int i = 0; i < n; i++) G[i].clear() ;
		edges.clear() ; 
	}
	
	void add_edge(int x, int y, int val) {
		edges.push_back((Edge){x, y, val});//没有构造函数的写法
		m = edges.size() ; 
		G[x].push_back(m-1);
	}

	void dijkstra (int s) {
		priority_queue <node> q;
		for(int i = 0; i < n; i++) d[i] = INF; 
		d[s] = 0;
		memset(vis, 0, sizeof(vis));
		q.push(node(0,s));//注意id与dis的顺序哦(别问我是怎么知道的
		while(!q.empty() ) {
			node tmp = q.top() ; q.pop() ;
			int u = tmp.id ;
			if(vis[u]) continue;
			vis[u] = true;
			for(int i = 0;i < G[u].size() ; i++) {
				Edge& e = edges[ G[u][i] ];//引用不加也行 
				if(d[e.y] > d[u] + e.val ) {
					d[e.y ] = d[u] + e.val ;
					p[e.y ] = G[u][i];
					q.push(node(d[e.y], e.y)) ;
				}
			}
		} 
	}
};

用stl+dij实现的应用 & 题目

要求输出路径的

统计任意两点之间的路径(包括起点&终点)

代码呢,就是上面的

...
p[e.y ] = G[u][i];
...					
//在struct Dji...里面加

  // dist[i]为s到i的距离,paths[i]为s到i的最短路径(经过的结点列表,包括s和t)
  void GetShortestPaths(int s, int* dist, vector<int>* paths) {
    dijkstra(s);
    for(int i = 0; i < n; i++) {
      dist[i] = d[i];
      paths[i].clear();
      int t = i;
      paths[i].push_back(t);
      while(t != s) {
        paths[i].push_back(edges[p[t]].from);
        t = edges[p[t]].from;
      }
      reverse(paths[i].begin(), paths[i].end());
    }
  }

例题:机场快线UVA 11374

题意: 在一张图中有两种路线,一种是经济线,一种是商业线,经济线可以随便走,但是商业线只能走一次。现在问从起点到终点的最短路是什么。

注意: 商业线也是双向的

// 题目相关(一开始写的丢了,只能去代码仓库找了...)
//作者: Rujia Liu
Dijkstra solver;
int d1[maxn], d2[maxn];
vector<int> paths1[maxn], paths2[maxn];

int main() {
  int kase = 0, N, S, E, M, K, X, Y, Z;
  while(scanf("%d%d%d%d", &N, &S, &E, &M) == 4) {
    solver.init(N);

    S--; E--; // 编号从0~N-1
    for(int i = 0; i < M; i++) {
      scanf("%d%d%d", &X, &Y, &Z); X--; Y--;
      solver.AddEdge(X, Y, Z);
      solver.AddEdge(Y, X, Z);
    }
    solver.GetShortestPaths(S, d1, paths1); // S到所有点的距离和路径
    solver.GetShortestPaths(E, d2, paths2); // T到所有点的距离和路径

    int ans = d1[E];              // 初始解解为直达距离
    vector<int> path = paths1[E]; // 初始解的station序列
    int midpoint = -1;            // 不坐商业线

    scanf("%d", &K);
    for(int i = 0; i < K; i++) {
      scanf("%d%d%d", &X, &Y, &Z); X--; Y--;
      for(int j = 0; j < 2; j++) { // j=0代表商业线坐X->Y,j=1代表Y->X
        if(d1[X] + d2[Y] + Z < ans) {
          ans = d1[X] + d2[Y] + Z;
          path = paths1[X];
          for(int j = paths2[Y].size()-1; j >= 0; j--) // 从Y到T的距离要反过来
            path.push_back(paths2[Y][j]);
          midpoint = X;
        }
        swap(X, Y);
      }
    }

    if(kase != 0) printf("
");
    kase++;

    for(int i = 0; i < path.size()-1; i++) printf("%d ", path[i]+1);
    printf("%d
", E+1);//格式有的严格....oj就这样,加油吧
    if(midpoint == -1) printf("Ticket Not Used
"); else printf("%d
", midpoint+1);
    printf("%d
", ans);
  }
  return 0;
}

求单源最短路树

把p看出父指针,则所有点形成了一棵树(有n-1条边,起点的p值等于它自己,其他点u对应一条边p

下次再写...留坑待补

统计路径

  1. 枚举两点间所有的最短路:

    做法: 先求出d, 然后从起点开始出发,但只沿着d[j] = d[i] + w(i,j)

  2. 统计最短路的条数:

    (f[i]表示从i到目标节点的最短路条数

    做法: f[i] = sum(f[j] | d[j] = d[i] + w(i,j) ) 边界条件: f[e终点]为1

  3. 温馨提示: 在最短路中,因为遍历的需要,常常从终点逆推这种情况下就要注意图的单双向了, 双向好说,可以直接按正常的来;单向的话...需要反向

    补:若是需要打印路径,建议在写的时候用一个vector;

例题: 林中散步UVA10917

题意:Jimmy打算每天沿着一条不同的路走,而且,他只能沿着满足如下条件的道路(A,B):存在一条从B出发回家的路径,比所有从A出发回家的路径都短,你的任务是计算有多少条不同的路径

注意,这里出发点为1--,目的点为2--。

/*熟悉高中恒成立问题的人都知道,题意如下:
他只想沿着(A,B) 且 d[B] < d[A] 的路径走
这样,我们在算出d之后创建一个新图: 仅当 d[B] < d[A]时, 加入(A,B)
这是个DAG, 用DP做*/
//题目相关 
int n,m;
Dijkstra solve;
int x,y,z;
int f[MAXN];

int dp(int x) {
	if(x == 1) return 1;//到家了 
	
	int& ans = f[x];//记忆化搜索中常用的,方便
	if(ans >= 0) return ans;
	
	int y;
	ans = 0; 
	for(int i = 0; i < solve.G[x].size() ; i++) {
		y = solve.edges[solve.G[x][i]].y;//双向的就是简单写 
		if(solve.d[y] < solve.d[x]) ans += dp(y);
	}
	return ans;
}

int main() {
	while(scanf("%d%d",&n,&m) == 2) {
		solve.init(n);
		for(int i = 0; i < m; i++) {
			scanf("%d%d%d",&x,&y,&z);
			x--,y--;
			solve.add_edge(x,y,z); 
			solve.add_edge(y,x,z);
		}
		solve.dijkstra(1);//家
		memset(f, -1, sizeof(f));
		//特别注意: 记忆化搜索中的特殊判定!!:要分清"取不到 " 和 “值为0” 
		//貌似这题没有等于0的,可以设为0 ,并将 ">= 0" 改下 
		printf("%d
", dp(0)); 
	}
	return 0;
}

dij的后记(补):

  1. 存在负边但无负环的最短路, dij不一定能求出解
  2. 无负边的, 多次dij可以求出任意两点之间的路径(如上

关于用链式前向星实现的最短路打印

方法很简单,在Edge里加个x , cnt, 分别为边的起点与边的编号, 因此,应该在初始化时,将edge的编号加上,之后的遍历方法就差不多了

原文地址:https://www.cnblogs.com/tyner/p/10965735.html