Zoj 3195 Design the city (数据结构_LCA)

题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=3320

题目大意:给定n个节点的树,要求我们查询树上三个点之间的距离。n <=5万 查询q<=7万

解题思路:LCA好题,和普通的LCA不同的是本题查询的是三个点。一开始我的做法很诡异,找三个点的关系,然后离线Tarjan求解。我共找到4个关系,但是总能找到反例拓展这些关系,写到后面if巨多代码就一坨了,代码一坨也就算了,还没AC,蛋疼菊紧!但是我很欣慰,做acm做这么久思维没有被算法死死束缚,我有自己的一些想法。

    Wa到死之后开始想怎么把三个点的查询转换成两个点的常规查询,其实拆成三次查询,然后这三次查询的结果加起来除以2就是答案了。为什么呢?求三次查询,查询到的路径覆盖最终的路径两次,你可以在纸上画画看。有了这个转换之后就简单了,答案用个结构体储存,查询x,y,z,如果当前查询x,y,那么更新resx,如果查询y,z,那么更新resy,如果查询x,y,那么更新resz,最后相加除2.

    用Tarjan离线算法ac了本题之后,想用LCA转RMQ解法来写下这题,就去学习学习。发现转换的方法其实挺简单的,我引用下这篇文章:http://blog.sina.com.cn/s/blog_5ceeb9ea0100kynz.html,里面讲得相当清楚。

    

对于有根树T的两个结点u、v,最近公共祖先LCA(T,u,v)表示一个结点x,满足x是u、v的祖先且x的深度尽可能大。另一种理解方式是把T理解为一个无向无环图,而LCA(T,u,v)即u到v的最短路上深度最小的点。
这里给出一个LCA的例子:
例一
对于T=<V,E>
V={1,2,3,4,5}
E={(1,2),(1,3),(3,4),(3,5)}
则有:
LCA(T,5,2)=1
LCA(T,3,4)=3
LCA(T,4,5)=3

RMQ问题与LCA问题的关系紧密,可以相互转换,相应的求解算法也有异曲同工之妙。下面给出LCA问题向RMQ问题的转化方法。

对树进行深度优先遍历,每当“进入”或回溯到某个结点时,将这个结点的编号存入数组E最后一位。同时记录结点i在数组中第一次出现的位置(事实上就是进入结点i时记录的位置),记做R[i]。如果结点E[i]的深度记做D[i],易见,这时求LCA(T,u,v),就等价于求E[RMQ(D,R[u],R [v])],(R[u]<R[v]),其中RMQ(D,R[u],R [v])就是在D数组中求下标从R[u]到R[v]的最小值的下标。例如一,求解步骤如下:

数列E[i]为:1,2,1,3,4,3,5,3,1

R[i]为:1,2,4,5,7

D[i]为:0,1,0,1,2,1,2,1,0

于是有:

LCA(T,5,2) = E[RMQ(D,R[2],R[5])] = E[RMQ(D,2,7)] = E[3] = 1

LCA(T,3,4) = E[RMQ(D,R[3],R[4])] = E[RMQ(D,4,5)] = E[4] = 3

LCA(T,4,5) = E[RMQ(D,R[4],R[5])] = E[RMQ(D,5,7)] = E[6] = 3

易知,转化后得到的数列长度为树的结点数的两倍减一, 所以转化后的RMQ问题与LCA问题的规模同次。

 

再举一个例子帮助理解:

   (1)

    / \

(2)   (7)

/ \     \

(3) (4)   (8)

    /   \

(5)    (6)

一个nlogn 预处理,O(1)查询的算法.

Step 1:

        按先序遍历整棵树,记下两个信息:结点访问顺序和结点深度.

        如上图:

        结点访问顺序是: 1 2 3 2 4 5 4 6 4 2 1 7 8 7 1 //共2n-1个值

        结点对应深度是: 0 1 2 1 2 3 2 3 2 1 0 1 2 1 0

Step 2:

        如果查询结点3与结点6的公共祖先,则考虑在访问顺序中

        3第一次出现,到6第一次出现的子序列: 3 2 4 5 4 6.

        这显然是由结点3到结点6的一条路径.

        在这条路径中,深度最小的就是最近公共祖先(LCA). 即

        结点2是3和6的LCA.

Step 3:

        于是问题转化为, 给定一个数组R,及两个数字i,j,如何找出

        数组R中从i位置到j位置的最小值..

        如上例,就是R[]={0,1,2,1,2,3,2,3,2,1,0,1,2,1,0}.

        i=2;j=7;

        接下来就是经典的RMQ问题.

 有了上面的基础,我们初始化好rmq,我们就可以在线o(1)的时间内输出答案,最终结果的处理方法和tarjan一样,三者求和除2.

测试数据:

4
0 1 1
1 2 2
1 3 3
1
1 2 3

4
0 1 1
0 2 2
0 3 3
1
1 2 3

6
0 1 1
0 2 2
1 3 3
1 4 4
5 2 5
1
4 5 3

4
0 1 1
1 2 2
2 3 3
2
0 1 2
1 2 3

4
0 1 1
0 2 2
1 3 3
2
0 1 2
1 2 3

7
0 1 1
1 2 2
1 3 3
2 4 4
4 5 5
3 6 6
3
1 2 3
0 2 3
0 4 6

7
0 1 1
0 2 2
1 3 3
1 4 4
2 5 5
2 6 6
5
0 1 3
1 0 2
3 0 6
4 1 5
4 2 5


C艹代码:

TarJan离线解法

#include <stdio.h>
#include <string.h>
#include <vector>
using namespace std;
#define MAX 150000
#define min(a,b) ((a)<(b)?(a):(b))


struct node {
    
    int y,z,len;
}cur;
struct Answer {
    
    int x,y,z;
    int resx,resy,resz;
}ans[MAX];
int dist[MAX],fa[MAX];
int cnt,n,q,visit[MAX];
vector<node> tree[MAX];
vector<node> query[MAX];


void Initial() {

    memset(dist,0,sizeof(dist));
    memset(ans,-1,sizeof(ans));
    memset(visit,0,sizeof(visit));
    for (int i = 0; i <= n; ++i)
        fa[i] = i,tree[i].clear(),query[i].clear();
}
int Find(int x) {

    int r = x,tp;
    while (r != fa[r]) r = fa[r];
    while (x != r) {

        tp = fa[x];
        fa[x] = r,x = tp;
    }
    return r;
}
void LCA(int now,int dis) {

    fa[now] = now;
    dist[now] = dis;
    visit[now] = 1;
    

    int i,size = tree[now].size();
    for (i = 0; i < size; ++i) {

        node cur = tree[now][i];
        if (!visit[cur.y]) {

            LCA(cur.y,dis+cur.len);
            fa[cur.y] = now;
        }
    }


    size = query[now].size();
    for (i = 0; i < size; ++i) {

        node cur = query[now][i];
        if (visit[cur.y] == 0) continue;


        int  in = cur.len;
        int  pa = Find(cur.y);
        if (now == ans[in].x && cur.y == ans[in].y 
                || now == ans[in].y && cur.y == ans[in].x)
            ans[in].resx = dist[now] + dist[cur.y] - 2 * dist[pa];
        if (now == ans[in].y && cur.y == ans[in].z
                || now == ans[in].z && cur.y == ans[in].y)
            ans[in].resy = dist[now] + dist[cur.y] - 2 * dist[pa];
        if (now == ans[in].x && cur.y == ans[in].z
                || now == ans[in].z && cur.y == ans[in].x)
            ans[in].resz = dist[now] + dist[cur.y] - 2 * dist[pa];
    }
}


int main()
{
    int i,j,k,flag = 0;
    int a,b,c,z;
    
    
    while (scanf("%d",&n) != EOF) {
        
        if (flag) printf("\n");
        Initial(),flag = 1;
        for (i = 1; i < n; ++i) {
            
            scanf("%d%d%d",&a,&b,&c);
            cur.y = b,cur.len = c;
            tree[a].push_back(cur);
            cur.y = a,cur.len = c;
            tree[b].push_back(cur);
        }
        
        
        scanf("%d",&q);
        for (i = 1; i <= q; ++i) {
            
            scanf("%d%d%d",&a,&b,&c);
            cur.len = i;
            ans[i].x = a,ans[i].y = b,ans[i].z = c;
            
            
            cur.y = b,query[a].push_back(cur);
            cur.y = a,query[b].push_back(cur);
            cur.y = a,query[c].push_back(cur);
            cur.y = c,query[a].push_back(cur);
            cur.y = b,query[c].push_back(cur);
            cur.y = c,query[b].push_back(cur);
        }
        
        
        LCA(0,0);
        for (i = 1; i <= q; ++i)
            printf("%d\n",(ans[i].resx + ans[i].resy + ans[i].resz)/2);
    }
}

LCA转RMQ解法

#include <stdio.h>
#include <string.h>
#include <vector>
using namespace std;
#define MAX 151000
#define min(a,b) ((a)<(b)?(a):(b))


struct node {
    
    int v,len;
}cur;
vector<node> tree[MAX];
int dist[MAX],deep[MAX];            //dist记录每个结点到根节点的距离,deep记录深搜过程每次出现的结点对应的深度
int Index[MAX],first[MAX];          //index记录深搜过程每次出现的结点对应的编号,first记录每个结点在深搜过程中第一次出现的位置
int ans,cnt,n,q,visit[MAX];         //cnt记录深搜过程进入和回溯了多少次,最后cnt等于2 * n - 1
struct RMQ{

    int dp[MAX][20];
    void Create();
    int  Query(int l,int r);
}rmq;
void RMQ::Create() {

    int i,j = 2,k = 0;
    for (i = 1; i <= cnt; ++i) dp[i][0] = i;


    while (j <= cnt) j *= 2,k++;
    for (j = 1; j <= k; ++j)
        for (i = 1; i + (1<<j) - 1 <= cnt; ++i) {

            int index1 = dp[i][j-1];
            int index2 = dp[i+(1<<(j-1))][j-1];
            if (deep[index1] < deep[index2])
                 dp[i][j] = index1;
            else dp[i][j] = index2;
        }
}
int RMQ::Query(int l, int r) {

    int i = 2,j,k = 0;
    while (i <= r - l + 1) i *= 2,k++;
    int index1 = dp[l][k];                      //dp记录的是下标
    int index2 = dp[r-(1<<k)+1][k];


    if (deep[index1] < deep[index2])            //返回deep小的那个下标
         return index1;
    else return index2;
}
int LCA(int a,int b) {

    int temp;
    if (first[a] < first[b])                    //找a和b第一次出现的位置,然后找小的位置到大的位置中间那一段的最小深度
         temp = rmq.Query(first[a],first[b]);
    else temp = rmq.Query(first[b],first[a]);


    return Index[temp];                         //返回的temp是deep数组的下标,通过index数组返回真实下标
}


void Initial() {

    cnt = 0;
    memset(dist,0,sizeof(dist));
    memset(visit,0,sizeof(visit));
    memset(first,0,sizeof(first));
    for (int i = 0; i <= n; ++i)
        tree[i].clear();
}
void Dfs(int s,int dep,int dis) {

    visit[s] = 1;     
    dist[s]  = dis;                 //记录距离
    Index[++cnt] = s;               //cnt对应着真实的s结点
    deep[cnt] = dep;                //记录编号cnt的深度
    if (!first[s]) first[s] = cnt;  //s第一次出现的位置是cnt


    int size = tree[s].size();
    for (int i = 0; i < size; ++i) {

        node cur = tree[s][i];
        if (visit[cur.v]) continue;


        Dfs(cur.v,dep+1,dis+cur.len);
        Index[++cnt] = s;           //回溯的时候也要更新
        deep[cnt] = dep;
    }
}


int main()
{
    int i,j,k,flag = 0;
    int a,b,c,z,fa;
    
    
    while (scanf("%d",&n) != EOF) {
        
        if (flag) printf("\n");
        Initial(), flag = 1;
        for (i = 1; i < n; ++i) {
            
            scanf("%d%d%d",&a,&b,&c);
            cur.v = b,cur.len = c;
            tree[a].push_back(cur);
            cur.v = a,cur.len = c;
            tree[b].push_back(cur);
        }
        
        
        Dfs(0,0,0);
        rmq.Create();
        scanf("%d",&q);
        for (i = 1; i <= q; ++i) {
            
            scanf("%d%d%d",&a,&b,&c);
            fa = LCA(a,b);
            ans  = dist[a] + dist[b] - 2 * dist[fa];
            fa = LCA(b,c);
            ans += dist[b] + dist[c] - 2 * dist[fa];
            fa = LCA(a,c);
            ans += dist[a] + dist[c] - 2 * dist[fa];
            printf("%d\n",ans / 2);
        }
    }
}

本文ZeroClock原创,但可以转载,因为我们是兄弟。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值