《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");
}
View Code

 

posted @ 2020-08-28 08:59  levill  阅读(185)  评论(0编辑  收藏  举报