【XSY2597】一样远(equal)

\(Description\)

企鹅国的城市结构是一棵树,有\(N\)座城市和\(N-1\)条无向道路,每条道路都一样长。豆豆和豆沙准备去参加\(NOIP(National Olympiad in Informatics for Penguin)\),但是他们住在不同的地方,豆豆住在城市\(A\),豆沙住在城市\(B\)。他们想找一个距离\(A\)\(B\)一样远的集合地点,所以他们想知道有多少个城市满足这个要求?

由于他们会参加很多次\(NOIP\),所以有很多个询问。


\(Input\)

第一行一个整数\(N\)代表城市个数。

接下来\(N-1\)行,每行两个数字\(F\)\(T\),表示城市\(F\)和城市\(T\)之间有一条道路。

接下来一行一个整数\(M\)代表询问次数。

接下来\(M\)行,每行两个数字\(A\)\(B\),表示这次询问的城市\(A\)和城市\(B\)\(A\)可能与\(B\)相同)。


\(Output\)

输出\(M\)行,每行一个整数表示到\(A\)\(B\)一样远的城市个数。


\(SampleInput1\)

4

1 2

2 3

2 4

2

1 2

1 3


\(SampleOutput1\)

0

2


\(SampleInput2\)

4

1 2

2 3

2 4

2

1 1

3 3


\(SampleOutput2\)

4

4


\(Hint\)

对于\(30\%\)的数据:\(N,M≤1000\)

对于另外\(10\%\)的数据:\(A=B\)

对于另外\(30\%\)的数据:保证树的形态随机;

对于\(100\%\)的数据:\(1≤N,M≤100000\)


思路

我们发现这道题给出的一棵树,于是我们考虑树上路径的算法:\(LCA\),树链剖分

我们在这里选择\(LCA\)

先声明一下\(LCA\)中常用的数组含义


\(d[a]\)表示节点\(a\)的深度

\(siz[a]\)表示节点\(a\)的子树大小(包括他自己)

\(f[a][i]\)表示\(a\)\(2^i\)父亲


我们考虑在什么情况下会存在与\(a,b\)相同距离的点。设这个点为\(c\)

可以发现,当且仅当\(a\)\(c\) 的距离等于\(b\)\(c\)的距离,c存在

并且,我们可以发现,\(c\) 必定在\(a\)\(lca\)的路径上或 \(b\)\(lca\) 的路径上

综上,我们可以得到结论:当且仅当 \(a\)\(b\) 的路径长度为偶数时,\(c\) 存在

\(a\)\(b\) 的路径长度 \(dis=d[a]+d[b]-d[lca]\times 2,dis\%2=0\)(至于为什么,自己想想)


如果满足条件

我们对于每一个询问的\(a,b\),考虑情况

\(1.\) \(a=b\) ,显而易见答案为树的节点个数\(n\)

给个总图:

在这里插入图片描述

\(2.\) \(a\neq b\)\(d[a]=d[b]\) , 即在图中 \(a=5,b=7\)

这样 \(c\) 必定为·\(lca\)

可以发现,他们在 \(2\) 节点相遇,之后的 \(1\) 节点的另外两棵子树上的点以及 \(14\) 节点都满足条件

于是, 得到 \(ans=n-siz[5]-siz[7]\)

\(3.\) \(a\neq b\)\(d[a]<d[b]\) ,即在图中 \(a=10,b=4\)

这样 \(c\)\(3\) 节点,之后的 \(13\) 节点满足条件

于是,得到 \(ans=siz[3]-siz[8]\)

\(4.\) \(a\neq b\)\(d[a]>d[b]\)

同上


考虑完情况后,我们又遇到一个问题:如何在 \(O(logn)\) 时间内找到 \(a,b\) 相遇的节点,并找到应该减去子树大小的节点

我们还是选择倍增查找

假设 \(d[a]<d[b]\)

\(c\) 必定在 \(b\)\(lca\) 的路径上

我们可以轻易得到 \(c\)\(b\) 的距离即 \(b\) 要向上跳的层数为\(dis/2(dis\%2=0)\)

向上跳 \(dis\) 层,跟 \(LCA\) 的实现相似,就可以实现了

详细见代码:


代码:

#include<bits/stdc++.h>
using namespace std;
const int N=100010;
int n;
int to[N<<1];
int nxt[N<<1];
int head[N];
int cnt=0;
void add(int u,int v)
{
    to[++cnt]=v;
    nxt[cnt]=head[u];
    head[u]=cnt;
}
int f[N][25];
int d[N];
int siz[N];
void dfs(int u,int fa)
{
    siz[u]=1;
    f[u][0]=fa;
    for(int i=1;i<=20;i++)f[u][i]=f[f[u][i-1]][i-1];
    for(int i=head[u];i;i=nxt[i])
    {
        int v=to[i];
        if(v!=fa)
        {
            d[v]=d[u]+1;
            dfs(v,u);
            siz[u]+=siz[v];
        }
    }
}
int get(int x,int y)
{
    if(d[x]<d[y])swap(x,y);
    for(int i=20;i>=0;i--)
    {
        if(d[f[x][i]]>=d[y])
        {
            x=f[x][i];
        }
    }
    if(x==y)return x;
    for(int i=20;i>=0;i--)
    {
        if(f[x][i]!=f[y][i])
        {
            x=f[x][i],y=f[y][i];
        }
    }
    return f[x][0];
}
//以上是LCA模板
int id=0x7fffffff;
int work(int &son,int a,int deep)//找到相遇的节点以及要减去子树大小的节点son,deep表示向上跳deep层
{
    int dep=deep-1;
    for(int i=20;i>=0;--i)
    {
        if(dep>(1<<i))
        {
            dep-=(1<<i);
            a=f[a][i];
        }
    }
    son=a;
    return f[a][0];
}
int main()
{
    scanf("%d",&n);
    int a,b;
    int tot=0;
    for(int i=1;i<n;i++)
    {
        scanf("%d %d",&a,&b);
        add(a,b);add(b,a);
    }
    d[1]=1;
    int m;
    dfs(1,0);
    scanf("%d",&m);
    while(m--)
    {
        scanf("%d %d",&a,&b);
        if(a==b)
        {
            printf("%d\n",n);
            continue;
        }
        int lca=get(a,b);
        int dis=d[a]+d[b]-d[lca]*2;
        if((dis%2)==1)puts("0");
        else 
        {
            dis/=2;
            int sona,sonb,v;
            if(d[a]==d[b])
            {
                work(sona,a,dis);
                work(sonb,b,dis);
                printf("%d\n",n-siz[sona]-siz[sonb]);
            }
            else 
            {
                if(d[a]<d[b])
                {
                    v=work(sonb,b,dis);
                    printf("%d\n",siz[v]-siz[sonb]);
                }
                else 
                {
                    v=work(sona,a,dis);
                    printf("%d\n",siz[v]-siz[sona]);
                    
                }
            }
        }
    }
    return 0;
}

转载于:https://www.cnblogs.com/ShuraEye/p/11412931.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值