poj 1986 Distance Queries(最近公共祖先的tarjan算法)

Distance Queries
Time Limit: 2000MS Memory Limit: 30000K
Total Submissions: 7626 Accepted: 2679
Case Time Limit: 1000MS

Description

Farmer John's cows refused to run in his marathon since he chose a path much too long for their leisurely lifestyle. He therefore wants to find a path of a more reasonable length. The input to this problem consists of the same input as in "Navigation Nightmare",followed by a line containing a single integer K, followed by K "distance queries". Each distance query is a line of input containing two integers, giving the numbers of two farms between which FJ is interested in computing distance (measured in the length of the roads along the path between the two farms). Please answer FJ's distance queries as quickly as possible! 

Input

* Lines 1..1+M: Same format as "Navigation Nightmare" 

* Line 2+M: A single integer, K. 1 <= K <= 10,000 

* Lines 3+M..2+M+K: Each line corresponds to a distance query and contains the indices of two farms. 

Output

* Lines 1..K: For each distance query, output on a single line an integer giving the appropriate distance. 

Sample Input

7 6
1 6 13 E
6 3 9 E
3 5 7 S
4 1 3 N
2 4 20 W
4 7 2 S
3
1 6
1 4
2 6

Sample Output

13
3
36

Hint

Farms 2 and 6 are 20+3+13=36 apart. 

Source


题意:字母使没有用的,第一个是点数,第二个是边数,下面m条边,然后是询问数,求2点的最近距离
题解:经典的最近公共祖先问题,用tarjan算法

#include<stdio.h>
#include<string.h>
int head[40005]; //原图邻接表头
int headques[40005]; //询问邻接表头
int dis[40005]; //各点到根的距离
int vis[40005];  //标记是否访问过
int fath[40005];  //并查集
int res[80005];  //保存询问结果
int all;  //原图边数
int allques;  //询问数
int n; //图的点数
int m;  //图的边数或询问数

struct edge{
    int next; //指向下一个子节点
    int data; //该子节点的下标
    int len;  //该边的长度
}p[80005];  //原图邻接表

struct ques{
    int next;  //指向下一个子节点
    int data;  //该子节点的下标
    int id;  //该询问的id
}pques[80005];  //询问邻接表
//原图构图
void add(int x,int y,int len)
{
    p[all].next=head[x];
    p[all].data=y;
    p[all].len=len;
    head[x]=all;
    all++;
}
//记录询问
void add2(int x,int y,int i)
{
    pques[allques].next=headques[x];
    pques[allques].data=y;
    pques[allques].id=i;
    headques[x]=allques;
    allques++;
}
//路径压缩并查集
int myfind(int x)
{
    if(x==fath[x])
        return x;
    fath[x]=myfind(fath[x]);
    return fath[x];
}
//预处理出各个节点到根的距离
void pre_dfs(int x,int len)
{
    int i;

    vis[x]=1;
    dis[x]=len;
    for(i=head[x];i!=-1;i=p[i].next)
    {
        if(!vis[p[i].data])  //由于加了2条边,防止回访父节点
            pre_dfs(p[i].data,len+p[i].len);
    }
}
//Tarjan算法求LCA
void Tarjan_LCA(int x)
{
    int i,lca;

    /*
    重点:
    显然这里第一个循环对图进行了深搜,并且只有搜索的当前子树的“根”节点
    的fath[]是等于自己的,其他都是等于它祖先的
    */
    fath[x]=x; //正在访问该节点的子树,讲fath[]指向自己
    vis[x]=1;
    for(i=head[x];i!=-1;i=p[i].next)
    {
        if(!vis[p[i].data]) //由于将无向边变成2条有向边,所以防止回访其父节点
        {
            Tarjan_LCA(p[i].data); //对p[i].data这个节点进行tarjan
            fath[p[i].data]=x; //p[i].data这个节点已经访问完,将其fath指向其祖先
        }
    }
    for(i=headques[x];i!=-1;i=pques[i].next)
    {
        /*
        若询问的另一个点已经被访问过,则他们的最近公共祖先必定是fath等于自己的节点,
        这是由于fath等于自己就代表现在正在询问这个子树现在正在询问的子树的“根”的另一个子树,
        所以这个必定是最近的公共祖先
        */
        if(vis[pques[i].data])
        {
            lca=myfind(pques[i].data);  //lca是询问的2个点的最近公共祖先
            res[pques[i].id]=dis[x]+dis[pques[i].data]-2*dis[lca];  //这个距离的结论是显然的
        }
    }
}

int main()
{
    int x,y,z,i;
    char s[8];

    //freopen("t.txt","r",stdin);
    while(scanf("%d%d",&n,&m)>0)
    {
        memset(head,-1,sizeof(head));
        for(all=i=0;i<m;i++)
        {
            scanf("%d%d%d%s",&x,&y,&z,s);
            add(x,y,z),add(y,x,z);  //构图,由于无向边,所以加2条有向边
        }

        memset(vis,0,sizeof(vis));
        pre_dfs(1,0);  //预处理出各个节点到根的距离,随便当一个节点是根,这里当1是根

        scanf("%d",&m);
        memset(headques,-1,sizeof(headques));
        for(allques=i=0;i<m;i++)
        {
            scanf("%d%d",&x,&y);
            add2(x,y,i),add2(y,x,i);  //记录询问和其id
        }

        memset(vis,0,sizeof(vis));
        Tarjan_LCA(1); //Tarjan算法求LCA

        for(i=0;i<m;i++) printf("%d\n",res[i]);
    }
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值