UVA

/*
  总体思路见小白书 P173 的讲解,不过小白书非常言简意赅,有点不好理解,所以我把自己的一些理解,加到了代码注释里
 

  收获:
  1. C++ 中,bool 类型的 true 等价于 int 类型的 1, bool 类型的 false 等价于 int 类型的 0
  参见 ( https://stackoverflow.com/questions/5369770/bool-to-int-conversion )
  
  摘取:
  If the source type is bool, the value false is converted to zero and the value true is converted to one.
  
  说明:
  1. 凡是加上了 (int) 强制类型转换的,基本都是因为,STL中的size()函数,返回的并不一定是int类型,按专业的说法,返回的应该是 size_t类型,所以还是应该转换的,虽然大部分时候,不强制转换好像也不会有错,但是,既然知道了这个知识点,最好还是严谨点

*/


#include <iostream>
#include <cstring>
#include <vector>
#include <queue>
#define rep(i, n) for ( int i = 0; i < (n); i++ )
using namespace std;

const int N = 1e5 + 5;
const int INF = 0x3f3f3f3f;

struct edge
{
	int u, v, c;
	edge ( int u = 0, int v = 0, int c = 0 ) : u(u), v(v), c(c)
	{
	}
};

vector<edge> edges;
vector<int> G[N]; 
vector<int> ans;
int n, vis[N];
int d[N];

void addEdge (int u, int v, int c)
{
	edges.push_back ( edge(u, v, c) );
	int idx = (int)edges.size() - 1;
	G[u].push_back(idx); // G[u] 记录了所有与 u 邻接的边,在 edges 中的下标 
}

// 反向 bfs, 以找到每个结点 i 到终点的最短距离 d[i]
void rev_bfs()
{
	memset( vis, 0, sizeof(vis) );
	d[n - 1] = 0;
	vis[n - 1] = true;
	
	queue<int> q;
	q.push(n - 1);
	while ( !q.empty() ) 
	{
		int v = q.front();
		q.pop();
		rep( i, (int)G[v].size() )
		{
			int e = G[v][i];
			int u = edges[e].v; 
			/*
			u 是所有与 v 相邻的结点,u 结点如果已经被访问过,说明最短距离 d[u] 已被赋值,且肯定比 v 想要赋给 u 的 d[v] + 1 要小 (这个是通过遍历的规律能推出来的,不过我不知道如何严谨证明),所以如果已经被赋值,我们就不去更改了;
			
			只对没有赋值的相邻结点作如下处理:更改其d[u],并设置标记 vis[u] (使其的 d[u]不会被 u 的其他相邻结点更改,因为 d[u] 不可能比当前更小了),并把 u 结点的标号压栈
			*/
			if ( !vis[u] ) 
			{
				vis[u] = true;
				d[u] = d[v] + 1;
				q.push(u);
			}
		}
	}
}

/*
 *从起点开始走,但是每次达到一个新结点时,要保证d值恰好减少 1, 如果有多种走法,选颜色字典序最小的走, 
 *相当于再做了一次 BFS 
*/
void bfs()
{
	memset( vis, 0, sizeof(vis) );
	vis[0] = true;
	ans.clear();
	
	vector<int> next;
	next.push_back(0);
	
	rep ( i, d[0] ) //要选取的路线,使得 d 值恰好减少1,而起点到终点的最短距离又是 d[0],故而循环次数就为 d[0]
	{
		int min_color = INF;
		rep ( j, (int)next.size() ) // next中存放了上一步的所有可能解 ( 可能有多条边颜色字典序最小,那么走下一步时,要把所有这些可能的解,都当作起点考虑一遍 )
		{
			int u = next[j];
			rep ( k, (int)G[u].size() ) //找到这些可能的“起点”的临边,逐一分析
			{
				int e = G[u][k];
				int v = edges[e].v;
				if ( d[u] == d[v] + 1 ) //如果有恰好能走一步的情况,则看能否更新当前的 min_color,可以则更新
				min_color = min ( min_color, edges[e].c );
			}
		}
		ans.push_back(min_color); //这是从所有可能的“起点”开始,考虑了其临边以后,我们得到的,这一步真正该走的颜色,压入该颜色
		
		
		/*
		因为最初可能有多个字典序最小,在 rep ( i, d[0] ) 循环中的 rep ( j, (int)next.size() )里,其实就是以这些点为起点,考虑其所走的下一步,该取哪个颜色;
		
		但是,走完下一步以后,其实还是有可能有多解,下面的这些代码,就是找出再走一步时的多解 (这些解的起点必定来自 next,且它们选取的颜色,肯定和要走的下一步,所选取的 min_color 颜色相等,且在走的过程中,必定也是 d 每次递减 1 的),把这些多解都压入 next2 这个 vector 中,并将其赋给 next 这个 vector,在 i 循环的下一轮循环里,我们就又从 next里的多解来考虑 ( 此时next 已经变成,走下一步时,保存的多解了 ),如此循环往复,每次往ans里面压入所选取的一个颜色,并再次从多解中,挑出真正满足“最小颜色”、“最小字典序”的解,这样不断地进行剔除,知道选出了从起点走向终点的,共 d[0] 步的走法
		
		*/
		vector<int> next2;
		rep( j, (int)next.size() )
		{
			int u = next[j];
			rep( k, (int)G[u].size() )
			{
				int e = G[u][k];
				int v = edges[e].v;
				
				if ( d[u] == d[v] + 1 && !vis[v] && edges[e].c == min_color )
				{
					vis[v] = true;
					next2.push_back(v);
				}
			}
		}
		next = next2; //话说,这个的思路,好像有点像,DP 里的滚动数组呢!~就是永远是两个容器来保存,进行下一轮时,要以另一个容器的值作为新的启示值
		//虽然好像和 DP 的滚动数组,还是有不少差别的,但我觉得思想上,可以类比帮助理解,因为这题比滚动数组还抽象,比喻一下以后,感觉更好理解了一些 
	}
	cout << (int)ans.size() << endl;
	cout << ans[0];
	for ( int i = 1; i < (int)ans.size(); i++) cout << " " << ans[i];
	cout << endl;
}

int main ()
{
	int u, v, c, m;
	while (cin >> n >> m)
	{
		edges.clear();
		rep(i, n) G[i].clear();
		
		while (m--)
		{
			cin >> u >> v >> c;
			addEdge(u - 1, v - 1, c);
			addEdge(v - 1, u - 1, c); //注意,该题是无向图问题,所以要将起点终点颠倒,再建一次边
		}
		rev_bfs(); // reverse-bfs 反向bfs
		bfs(); 		
	}
	return 0;
}


原文地址:https://www.cnblogs.com/mofushaohua/p/7789388.html