题意:给定n(<=100000)个点(1......n),m(<=200000)条边,每条边有个颜色值,试求从点1到点n经过的边数最少,在此前提下,经过的边数的颜色序列的字典树最小。
思路:如果只是边数最少直接一个bfs即可求之。现在多了个条件边数的颜色值序列的字典树最小,可以先选颜色值小的?不能,因为你不知道从颜色最小的走是否走的到,或者步数最短呢。
我们逆向思考,如果我们从终点bfs一下,记录每个点到终点的最短距离,然后我们从起点出发,每一步只往(距离-1)的节点走,因为,这样才能确保最短路径,然后,把距离相同的看作一层,从起点出发,刚开始第一次只有起点,往下一层所有节点中的最小颜色值走,没走一层确定的一个答案,如果该层最小颜色值有多个,我们不能确定到底走哪个,具体还要看下下层,因此,我们把最小颜色值所有节点看作新的一层,覆盖旧的,相当于起点从一个变成多个。这样迭代。
2.对于每个节点我们最多只放入队列一次,所以用个数组标记,不然TLE。
/*
wrong:
9 9
1 2 1
1 3 1
2 4 2
2 5 3
3 6 1
6 8 5
4 7 1
7 9 2
8 9 2
*/
#include<stdio.h>
#include<math.h>
#include<stdlib.h>
#include<algorithm>
#include<string.h>
#include<vector>
#include<queue>
using namespace std;
typedef long long LL;
const int maxn = 100010;
int n,m;
int step[2*maxn];
int ans [2*maxn];
int vis[2*maxn];
int T ;
const int INF = 1e9+5;
int ans_L;
struct Edge{
int Next;
int mark;
Edge(int Next_ = -1,int mark_ =-1):Next(Next_),mark(mark_){}
};
vector<Edge> G[maxn];
queue<int > q;
void addEdge(int u,int v,int mark){
G[u].push_back(Edge(v,mark) );
}
void bfs(){
int pre;
int i,t,len,edge;
while(!q.empty()) q.pop();
q.push(n);
// for(i=0;i<10;i++){
// printf("%d ",step[i]);
// }
step[n] = 0;
while(!q.empty()){ //给每个节点记录到目标节点的距离
pre = q.front();
q.pop();
len = G[pre].size();
//printf("len = %d ",len);
for(i=0; i<len; i++){
t = G[pre][i].Next;
//printf("%d %d\n",pre,t);
if(step[t] == -1){
step[t] = step[pre] +1;
q.push(t);
}
}
}
}
void bfs_ans(){ /*思路有问题,有可能求出的答案不是一条路径
因为该方法是根据每一个数取其所有下一步节点的最小值,应该是取step值一样的所有下一步节点的最小值
因为我们需要找一条路径*/
int pre;
int i,t,len,edge,mini,j;
while(!q.empty()) q.pop();
vis[1] = 1;
q.push(1);
while(!q.empty()){ //从多条最短路径中寻找字典序最小的那条
pre = q.front();
q.pop();
len = G[pre].size();
mini = 1e9+5;
//ans[step[pre]] = mini = 1e9+5; 不能这么初始化,因为有可能之前ans[step[pre]]被赋值过
for(i=0;i<len;i++){
if(step[G[pre][i].Next] == step[pre] - 1){
mini = min (mini,G[pre][i].mark);
}
}
for(i=0;i<len;i++){ //根据最短的那条边,我们可以把可能经过的点存入队列,注意不要重复
if(step[G[pre][i].Next] == step[pre] - 1){
if(G[pre][i].mark == mini && step[G[pre][i].Next] != 0){
if(!vis[G[pre][i].Next]){ /*注释掉超时 */
vis[G[pre][i].Next] = !vis[G[pre][i].Next];
printf("%d %d\n",pre,G[pre][i].Next);
q.push(G[pre][i].Next);
}
}
}
}
printf("#%d %d\n",step[pre],mini);
ans[step[pre]] = min(mini,ans[step[pre]]);
}
}
void bfs_a(){
int deep = step[1];
int i,d,v,ch,L,len,pre,mini,j;
vector<int > v1;
vector<int > v2;
v1.push_back(1);
for(d=deep;d>=1;d--){ //floor
len = v1.size();
// for(i=0;i<len;i++){
// printf("%d ",v1[i]);
// }
// puts("!");
mini = INF;
for(i=0;i<len;i++){ //pre
pre = v1[i];
// printf("pre = %d\n",pre);
L = G[pre].size();
for(j=0;j<L;j++){ //ch
ch = G[pre][j].Next;
//printf("ch = %d\n",ch);
if(step[ch] == step[pre]-1 && G[pre][j].mark < mini){
mini = G[pre][j].mark;
}
}
}
// printf("d = %d %d\n",d,mini);
for(i=0;i<len;i++){
pre = v1[i];
L = G[pre].size();
for(j=0;j<L;j++){ //ch
ch = G[pre][j].Next;
if(step[ch] == step[pre]-1 && G[pre][j].mark == mini && !vis[ch]){ //vis数组去掉还是TLE
vis[ch] = 1;
v2.push_back(ch);
}
}
}
v1 = v2;
v2.clear();
// printf("mini %d %d\n",d,mini);
ans[d] = mini;
}
}
void pritf(int cur){
int len = G[cur].size();
for(int i=0;i<len;i++){
printf("%d %d\n",cur,G[cur][i].Next);
}
system("pause");
}
int main()
{
int i,j,k;
int u,v,mark;
while(scanf("%d%d",&n,&m) == 2){
for(i=0;i<maxn;i++){
G[i].clear();
}
for(i=0;i<m;i++){
scanf("%d%d%d",&u,&v,&mark);
addEdge(u,v,mark);
addEdge(v,u,mark);
}
//pritf(1);
memset(step,-1,sizeof(step));
bfs();
// for(int i=1;i<=n;i++){
// printf("%d %d\n",i,step[i]);
// }
// system("pause");
ans_L = step[1];
memset(vis,0,sizeof(vis));
bfs_a();
printf("%d\n%d",ans_L,ans[ans_L]);
for(i=ans_L-1;i>=1;i--){
printf(" %d",ans[i]);
}
puts("");
}
return 0;
}
!!!!!
注意点:1. 根据每个节点来扩展其所有孩子,一个queue来根据每个节点距离值来更新答案,对于每个节点,我们找到其最小的颜色值相连的孩子节点,然后把该节点放入队列,利用该颜色值更新答案。但是这样做问题在于,最终的找到的路径不是联通的,只是确保一层接着一层,以下数据为例,从1号出发,到达2,3号节点的颜色值相同,更新a【0】,然后把2,3放入队列,接着以2为起点,4,5号节点选择最小的颜色值4号节点,更新a【1】,以3起点找到经过颜色值最小边的孩子,更新a【1】,然后以4号节点为根,更新a【2】,!!!!错了,因为我们是走的3好节点,所以四号节点没有价值了。
/*
wrong:
9 9
1 2 1
1 3 1
2 4 2
2 5 3
3 6 1
6 8 5
4 7 1
7 9 2
8 9 2
*/