Bubble Cup 8 finals G. Run for beer (575G)
题意:
给定一个带权无向图,每条边的代价为边权/当前速度,每次到达一个新节点,速度都会除以10。
求0号点到n-1号点的最小代价,如果多解输出点数最少的解,输出代价、路径点数、路径经过的点。
1<=边数<=10^5, 1<=点数<=10^5, 0<=边权<=9
题解:
比较有趣的一道题…难度不算太高,但一些地方比较容易想错…
这题的边代价可变本身并不是什么大问题,因为是图中所有边一起变大,
但是一开始就会遇到一个问题,就是总代价数字过大,没有办法直接存储和比较。
观察到边权的奇怪的条件,就会发现代价其实就是经过的边权拼起来的十进制数。
那么就有办法进行比较了。
对于任意两条到底终点的路径,如果没有前导零,一定是短的优于长的。
所以可以用BFS来处理。
首先为了排除前导零,从终点开始只走权值为0的边来BFS,求出每个可以无代价到达终点的点的路径。
然后从起点开始进行BFS。这个地方最容易错。
BFS时一般只知道和当前点直接相连的点和边的信息,所以可以先考虑理想情况,
即起点到当前点和目标点经过的路径中除了当前目标点以外的代价是相同的。
这时只需比较当前边的权是否小于目标点的代价中的最高位即可。
还有一种比较容易处理的情况是当前点的路径长度已经大于目标点路径,那么不更新。
然后还有一种情况就是当前点路径长度小于目标点,
因为是BFS,所以只有首次访问目标点时出现。(初始化为无穷大)
剩下的一种情况就是前面说的理想情况的一般化,不能保证之前的路径代价相同。
出于时间复杂度考虑,暴力比较之前的路径是不妥的。
但是我们可以想到一种利用顺序的策略,BFS是逐层搜索,
那么如果每一层都按照当前层的代价从小到大搜索,
那么每搜索到一个点,都会是到这个点(除这层的代价外)最小的代价。
所以后面的点如果当前层代价一样(原本是需要麻烦的比较的情况)就不会更新这个点的状态,
如果当前层代价小就会更新这个状态(高位的优先度高),
所以这种麻烦的情况就不存在了。
实现的话可以开一个滚动的桶,桶里装一个包含某层代价为x的点的队列,从小到大处理即可。
时间复杂度O(n)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 #include <cstdio> #include <cstring> #include <queue> #include <utility> using namespace std; inline int read() { int s = 0; char c; while((c=getchar())<'0'||c>'9'); do{s=s*10+c-'0';}while((c=getchar())>='0'&&c<='9'); return s; } const int N = 100010; int n,m,tot,s[N],l[N],l0[N],fr0[N],fr[N],h[N],ans[N],ansp; struct eg{int dt,w,nx;}e[N*2]; queue<int> q,qa[2][10]; bool inq[N],inq0[N],z[N]; void link(int aa,int bb,int ww) { e[++tot].nx = h[aa]; e[tot].dt = bb; e[tot].w = ww; h[aa] = tot; e[++tot].nx = h[bb]; e[tot].dt = aa; e[tot].w = ww; h[bb] = tot; } void zerobfs() { q.push(n-1); fr0[n-1] = -1; inq0[n-1] = 1; while(!q.empty()) { int hn = q.front(); q.pop(); for(int nx,pt=h[hn];pt;pt=e[pt].nx) if(!e[pt].w&&!inq0[nx=e[pt].dt]) q.push(nx), inq0[nx] = 1, fr0[nx] = hn, l0[nx] = l0[hn]+1; } } void bfs() { qa[0][0].push(0); memset(s,0x7f,sizeof s); memset(l,0x7f,sizeof l); s[0] = 0; l[0] = 0; fr[0] = -1; z[0] = 1; int curp = 1; while(1) { curp ^= 1; while(!q.empty()) { int hn = q.front(); q.pop(); if(!inq[hn]) qa[curp][s[hn]].push(hn), inq[hn] = 1; } bool flag = 1; for(int i=0;i<=9;i++) { while(!qa[curp][i].empty()) { int hn = qa[curp][i].front(); qa[curp][i].pop(); flag = 0; for(int nx,pt=h[hn];pt;pt=e[pt].nx) { nx = e[pt].dt; if(l[hn]+1<l[nx]||(l[hn]+1==l[nx]&&(e[pt].w<s[nx]))) { s[nx] = e[pt].w, l[nx] = l[hn]+1, fr[nx] = hn; if(!inq[nx]) q.push(nx); } } } } if(flag) break; } } int main() { int i,j=0; n = read(); m = read(); for(i=1;i<=m;i++){ int aa = read(), bb = read(); link(aa,bb,read()); } zerobfs(); bfs(); ansp = n-1; for(i=0;i<n;i++) if(inq0[i]) if(l[i]<l[ansp]||(l[i]==l[ansp]&&s[i]<s[ansp])|| (l[i]==l[ansp]&&s[i]==s[ansp]&&l[i]+l0[i]<l[ansp]+l0[ansp])) ansp = i; for(i=ansp;i!=n-1;i=fr0[i]) fr[fr0[i]] = i, s[fr0[i]] = 0; bool flag = 0; for(i=n-1;i;i=fr[i]) { if(flag||s[i]) putchar('0'+s[i]), flag = 1; ans[++j] = i; } if(!flag) putchar('0'); printf("\n%d\n",l[ansp]+l0[ansp]+1); ans[++j] = 0; for(i=j;i>1;i--) printf("%d ",ans[i]); printf("%d\n",ans[1]); return 0; }