《TZOJ5838Freda的传呼机》
思维量很大的一个题。
对于A类的数据,显然就是一棵树,那么LCA找下距离差即可。
对于B类的数据,是环套树。个人认为和C类数据差的不是很多了。
只是这里因为只有一个环,所以可以暴力点,把环上的所有边都先找到然后特殊处理即可。
对于C类数据,经典的仙人掌图,考虑拆环的解法。
对于每个环,拆环为树,然后环上的点和环顶连边,环上的点和环顶的距离是该点到环顶的最短距离,显然这个距离是两个方向走法里最短的。
然后我们再按原先的边去建新图,即已经在同一个环内的点,不需要再连边。
建好新图之后,就是一棵树了,然后就可以统计距离了。这里因为是拆环成的树,所以统计距离的深度可能会错。
所以在倍增处理的同时,处理出跳的距离即可。
因为拆环后的树父节点和子节点深度差距肯定为1,所以不可能在倍增跳的中间就到了一个环中。
所以最后判断一下两个点是否在同一个环中,如果在就用环上的最小差距。
我们用loop[x][2]表示正向距离环顶的距离,loop[x][3]表示反向距离环顶的距离。
那么两个点环上的最小差距肯定就是min(loop[x][2]+loop[y][3],loop[x][3]+loop[y][2],abs(loop[x][2]-loop[y][2]))
然后是关于tarjan的很多细节。对于环的距离,我们先处理出整条环的长度,然后一个方向去回溯就可以得出两个方向的距离。
然后注意和环顶的连边,因为环顶可能属于多个环,所以环顶是不和任何点连边的。然后注意退环操作,来保证环上点的连续性。
还需要注意的是,1号点不会被加入栈,所以一开始要将1号点加入,不然回溯过程中如果1号点是环顶,就会导致无法停下。
// Author: levil #include<bits/stdc++.h> using namespace std; typedef long long LL; typedef pair<int,int> pii; const int N = 1e4+5; const int M = 12005; const LL Mod = 2008; #define rg register #define pi acos(-1) #define INF 1e9 #define CT0 cin.tie(0),cout.tie(0) #define IO ios::sync_with_stdio(false) #define dbg(ax) cout << "now this num is " << ax << endl; namespace FASTIO{ inline LL read(){ LL x = 0,f = 1;char c = getchar(); while(c < '0' || c > '9'){if(c == '-') f = -1;c = getchar();} while(c >= '0' && c <= '9'){x = (x<<1)+(x<<3)+(c^48);c = getchar();} return x*f; } void print(int x){ if(x < 0){x = -x;putchar('-');} if(x > 9) print(x/10); putchar(x%10+'0'); } } using namespace FASTIO; void FRE(){ /*freopen("data1.in","r",stdin); freopen("data1.out","w",stdout);*/} int n,m,q,cnt = 0,num = 0,tim = 0,clr = 0,tot = 0; int head[N],cur[N],dfn[N],loop[N][5],f[N][20],dis[N][20],lg[N],dep[N];//loop[0]-环的颜色,loop[1]-环顶点,loop[2],loop[3]两边的距离 pii top[N];//点,边权 struct Node{int to,dis,next;}e[M<<1],E[M<<1]; struct Edge{int st,to,val;}edge[M]; inline void init() { for(rg int i = 1;i < N;++i) lg[i] = lg[i-1] + ((1<<lg[i-1]) == i); } inline void add(int u,int v,int w) { e[++cnt].to = v,e[cnt].dis = w,e[cnt].next = head[u],head[u] = cnt; } inline void n_add(int u,int v,int w) { E[++num].to = v,E[num].dis = w,E[num].next = cur[u],cur[u] = num; } void tarjan(int u,int fa) { dfn[u] = ++tim; for(rg int i = head[u];i;i = e[i].next) { int v = e[i].to,d = e[i].dis; if(v == fa) continue; if(dfn[v] < dfn[u] && dfn[v] != 0)//说明v是环的顶点,可能还没赋值就判断了 { int len = e[i].dis;//环的总长,即一圈长度 for(rg int j = tot;top[j].first != v;--j) len += top[j].second; clr++; loop[u][0] = clr; loop[u][1] = v; loop[u][2] = d; loop[u][3] = len-d; int cost = min(loop[u][2],loop[u][3]); n_add(u,v,cost); n_add(v,u,cost); for(rg int j = tot-1;top[j].first != v;--j)//从tot-1开始,因为栈顶的u已经被处理 { loop[top[j].first][0] = clr; loop[top[j].first][1] = v; loop[top[j].first][2] = loop[top[j+1].first][2]+top[j+1].second;//连环,因为从tot-1开始退,所以边权是加的上一个点和前面的点的权值 loop[top[j].first][3] = len-loop[top[j].first][2]; int cost = min(loop[top[j].first][2],loop[top[j].first][3]); n_add(v,top[j].first,cost);//和环顶连边建新图 n_add(top[j].first,v,cost); } } if(dfn[v]) continue; top[++tot].first = v; top[tot].second = d; tarjan(v,u); } tot--;//退栈,对于不在环上的点和已经处理完的环上的点,显然要退栈,事实上除了和栈顶相邻的点,其他的点都会直接推栈 } //可以发现对于tarjan连环中,环顶是不属于任何一个环的,因为它可能属于多个环。 void dfs(int u,int fa,int dd) { dep[u] = dep[fa]+1; f[u][0] = fa,dis[u][0] = dd; for(rg int i = 1;i <= lg[dep[u]];++i) { f[u][i] = f[f[u][i-1]][i-1]; dis[u][i] = dis[f[u][i-1]][i-1]+dis[u][i-1]; } for(rg int i = cur[u];i;i = E[i].next) { int v = E[i].to,d = E[i].dis; if(v == fa) continue; dfs(v,u,d); } } LL LCA(int x,int y) { LL ans = 0; if(dep[x] < dep[y]) swap(x,y); while(dep[x] > dep[y]) { ans += dis[x][lg[dep[x]-dep[y]]-1]; x = f[x][lg[dep[x]-dep[y]]-1]; } if(x == y) return ans; for(rg int i = lg[dep[x]]-1;i >= 0;--i) { if(f[x][i] != f[y][i]) { ans += dis[x][i]+dis[y][i]; x = f[x][i],y = f[y][i]; } } if(loop[x][0] == loop[y][0] && loop[x][0] != 0 && loop[y][0]) { ans += min(loop[x][2]+loop[y][3],min(loop[x][3]+loop[y][2],abs(loop[x][2]-loop[y][2]))); } else ans += dis[x][0]+dis[y][0]; return ans; } int main() { init(); n = read(),m = read(),q = read(); for(rg int i = 1;i <= m;++i) { int u,v,w; u = read(),v = read(),w = read(); add(u,v,w);add(v,u,w); edge[i].st = u,edge[i].to = v,edge[i].val = w; } top[++tot].first = 1; top[tot].second = 0; tarjan(1,0);//环上的点和环顶连边 for(rg int i = 1;i <= m;++i) { if(loop[edge[i].st][0] == loop[edge[i].to][0] && loop[edge[i].st][0] != 0 && loop[edge[i].to][0] != 0) continue;//在同一个环内,不需要再连边 if(loop[edge[i].st][1] == edge[i].to || loop[edge[i].to][1] == edge[i].st) continue;//如果某个点是某个点的环顶,那么不用再连。 n_add(edge[i].st,edge[i].to,edge[i].val); n_add(edge[i].to,edge[i].st,edge[i].val); } dfs(1,0,0);//倍增 while(q--) { int x,y;x = read(),y = read(); LL ans = LCA(x,y); printf("%lld\n",ans); } system("pause"); }