UVA

来源:互联网 发布:qq三国陆逊打技能数据 编辑:程序博客网 时间:2024/06/10 09:29
/*  总体思路见小白书 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 反向bfsbfs(); }return 0;}