Link:http://acm.nyist.net/JudgeOnline/problem.php?pid=1006
偷西瓜
时间限制:
1000 ms | 内存限制:
65535 KB
难度:
4
-
描述
-
对于农村的孩子来说最大的乐趣,莫过于和小伙伴们一块下地偷西瓜了,虽然孩子们条件不是很好,但是往往他们很聪明,他们总在计算着到达瓜田的距离,以及逃跑的路线,他们总是以最短的距离冲到瓜田里面,然后以最短的距离回到出发的地方,不过瓜田的大人们已经在他们来的路上等待他们。于是聪明的小伙伴们便不走过的路,即每条路只走一遍,如果小伙伴们回不到出发的地方,他们就说“eating”,
我们假设 有 n (n<=100)个 村庄 m条路(m<=1000)小伙伴们总是从1号村庄出发,而瓜田总是在n号村庄.如果小伙伴们到达不了n号村庄,或者回不到1号村庄请输出"eating";
-
输入
-
多组数据
第一行一个整数 n
第二行 一个整数 m
随后的m行 有 三个数u,v,w 表示u 到 v村庄的距离为w(w<=1000);
输出
- 求小伙伴们从1号村庄出发,到 n号村庄,再回到1号村庄所用的最短距离,如果不能回到1号村庄请输出“eating”. 样例输入
-
2 1 1 2 999 3 3 1 3 10 2 1 20 3 2 50
样例输出
-
eating 80
上传者
-
ACM_王亚龙
解题思想:通过两次SPFA求最短路和次短路,该题比较麻烦的是如何实现删除第一次走过的最短路径(删除第一次走过的边)。具体操作详见代码注释部分。
AC code:
#include <iostream> #include <cstdio> #include <cstring> #include <queue> #define MAXN 1000010 using namespace std; const int INF=0x3f3f3f3f; struct node { int from; int to; int w; int next; }edge[MAXN]; int tot,n;//tot为边的数目,边编号从0开始 int cnt[MAXN];//入队次数,当≥顶点数n时成环,不再进队列 bool inq[MAXN];//标记是否在队列中 int head[MAXN];//head[i]表示以i为起点的边所对应的边编号,用链式前向星时访问边的顺序是倒着遍历的 int dis[MAXN];//dis[i]表示从源点到点i的最短距离 int pre_edge[MAXN];//记录路径用,当从第一个点到达第二个点时,将这两点之间连接的边编号记录下来, bool visited[MAXN];//pre_edge[i]表示刚走过的到达i的边所在的边编号,即记录走过的路径 void init() { memset(head,-1,sizeof(head)); memset(visited,false,sizeof(visited));//visited[i]标记该边编号为i的边是否已走过,遍历边时 tot=0; //通过判断是否标记已走过,从而实现"惰性删除" 路径 } void add(int from,int to,int w)//加边 { edge[tot].from=from; edge[tot].to=to; edge[tot].w=w; edge[tot].next=head[from]; head[from]=tot++; } bool spfa(int st) { deque<int>q; memset(dis,INF,sizeof(dis)); memset(cnt,0,sizeof(cnt)); memset(inq,false,sizeof(inq)); memset(pre_edge,-1,sizeof(pre_edge)); q.push_back(st); inq[st]=true; cnt[st]++; dis[st]=0; while(!q.empty()) { int now=q.front(); q.pop_front(); inq[now]=false; if(cnt[now]>=n)//入队次数,当≥顶点数n时成环,不再进队列 continue; if(cnt[now]==n)//成环,最短距离标记为无穷小,不再进队列 { dis[now]=-INF; } for(int i=head[now];i!=-1;i=edge[i].next) { if(visited[i])//通过判断是否标记已走过,从而实现"惰性删除" 路径 continue; int nex=edge[i].to; if(dis[nex]>dis[now]+edge[i].w) { dis[nex]=dis[now]+edge[i].w; pre_edge[nex]=i;//pre_edge[i]表示刚走过的到达i的边所在的边编号,即记录走过的路径 if(!inq[nex]) { inq[nex]=true; cnt[nex]++; if(!q.empty()&&dis[nex]<dis[q.front()])//利用deque来实现SPFA,入队时可以选择入队尾还是队首, { //从而进行SLF优化,提高效率 q.push_front(nex); } else { q.push_back(nex); } } } } } return true; } void del()//惰性删除边 { int i=pre_edge[n]; while(i!=-1)//通过从终点记录的边的前一个顶点不断回溯上去,直到到达源点,中间经过的边编号即为逆着的走过的路径 { //printf("edge:%d is visited\n",i); visited[i]=true;//经过的边编号即为逆着的走过的路径,故标记为已走过 int from=edge[i].from;//from表示走过的边所连接的两个端点中较先经过的那个端点编号 i=pre_edge[from];//走过的到达端点号from的边所在的边编号,一直回溯,直到到达源点,以源点结尾的边的编号初始化为-1, } //所以回溯到边编号i等于-1时表示到达了源点 } int main() { int m,u,v,w,ans; while(scanf("%d",&n)!=EOF) { scanf("%d",&m); init(); while(m--) { scanf("%d%d%d",&u,&v,&w); add(u,v,w); add(v,u,w);//无向图,要加双向边 } ans=0; spfa(1); if(dis[n]>=INF)//第一次走时没有到达n的最短路径 printf("eating\n"); else { // printf("%d\n",dis[n]); ans+=dis[n]; del();//惰性删除第一次走过的路径(边) spfa(1); if(dis[n]>=INF)//删除第一次走过的路径(边)后,第二次走时不存在从源点到n的最短路径(相对第一次而言是次短路径) printf("eating\n"); else { //printf("%d\n",dis[n]); ans+=dis[n]; printf("%d\n",ans); //del(); } } } return 0; }
-
多组数据