zoukankan      html  css  js  c++  java
  • 《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
    ",ans);
        }
        system("pause");
    }
    View Code
  • 相关阅读:
    [WinJS] Promise 用法
    Python 统计代码行
    mac下Apache + MySql + PHP网站开发
    android中,获取网速的方法实现
    如何屏蔽掉两个activity切换时的动画效果
    dp与px的相互转化
    毫秒的格式化
    关于android中事件传递和分发的一些小理解
    汉字转拼音
    关于实现无限循环的做法
  • 原文地址:https://www.cnblogs.com/zwjzwj/p/13575561.html
Copyright © 2011-2022 走看看