【HDU4822】【XSY2021】Tri-war

21 篇文章 0 订阅

如果题目中只有两个国家,事情就非常简单了:假设只有国家 A A A B B B,我们先找出 A A A B B B的最近公共祖先 l c a lca lca,然后找到在路径 A ⟶ l c a ⟶ B A\longrightarrow lca\longrightarrow B AlcaB上的中点 m i d mid mid,然后分3类讨论:

  1. d i s ( A , m i d ) = = d i s ( B , m i d ) dis(A,mid)==dis(B,mid) dis(A,mid)==dis(B,mid),则 A A A在这条路径上能到达的范围是 [ A , m i d ) [A,mid) [A,mid) B B B在这条路径上能到达的范围是 ( m i d , B ] (mid,B] (mid,B]。那么 A A A在全树所能到达的点数为:

    { s i z e [ s o n ] if  d e e p [ A ] > d e e p [ B ] ( s o n = 在 路 径 [ m i d , A ] 上 的 m i d 的 儿 子 ) n − s i z e [ m i d ] if  d e e p [ B ] > d e e p [ A ] \begin{cases} size[son] & \text{if }deep[A]>deep[B] & (son=在路径[mid,A]上的mid的儿子)\\ n-size[mid] & \text{if }deep[B]>deep[A] \end{cases} {size[son]nsize[mid]if deep[A]>deep[B]if deep[B]>deep[A](son=[mid,A]mid)

    B B B在全树能到达的点数为:

    { n − s i z e [ m i d ] if  d e e p [ A ] > d e e p [ B ] s i z e [ s o n ] if  d e e p [ B ] > d e e p [ A ] ( s o n = 在 路 径 [ m i d , B ] 上 的 m i d 的 儿 子 ) \begin{cases} n-size[mid] & \text{if }deep[A]>deep[B]\\ size[son] & \text{if }deep[B]>deep[A] & (son=在路径[mid,B]上的mid的儿子) \end{cases} {nsize[mid]size[son]if deep[A]>deep[B]if deep[B]>deep[A](son=[mid,B]mid)

  2. d i s ( A , m i d ) < d i s ( B , m i d ) dis(A,mid)<dis(B,mid) dis(A,mid)<dis(B,mid),则 A A A在这条路径上能到达的范围是 [ A , m i d ] [A,mid] [A,mid] B B B在这条路径上能到达的范围是 ( m i d , B ] (mid,B] (mid,B]。那么 A A A在全树所能到达的点数为:

    { s i z e [ m i d ] if  d e e p [ A ] > d e e p [ B ] n − s i z e [ s o n ] if  d e e p [ B ] > d e e p [ A ] ( s o n = 在 路 径 [ m i d , B ] 上 的 m i d 的 儿 子 ) \begin{cases} size[mid] & \text{if }deep[A]>deep[B]\\ n-size[son] & \text{if }deep[B]>deep[A] & (son=在路径[mid,B]上的mid的儿子) \end{cases} {size[mid]nsize[son]if deep[A]>deep[B]if deep[B]>deep[A](son=[mid,B]mid)

    B B B在全树能到达的点数为:

    { n − s i z e [ m i d ] if  d e e p [ A ] > d e e p [ B ] s i z e [ s o n ] if  d e e p [ B ] > d e e p [ A ] ( s o n = 在 路 径 [ m i d , B ] 上 的 m i d 的 儿 子 ) \begin{cases} n-size[mid] & \text{if }deep[A]>deep[B]\\ size[son] & \text{if }deep[B]>deep[A] & (son=在路径[mid,B]上的mid的儿子) \end{cases} {nsize[mid]size[son]if deep[A]>deep[B]if deep[B]>deep[A](son=[mid,B]mid)

  3. d i s ( A , m i d ) > d i s ( B , m i d ) dis(A,mid)>dis(B,mid) dis(A,mid)>dis(B,mid),那就把 A A A B B B交换一下就和情况2一样了。

那么对于三个点的情况,只需对于每个点求出与另外两个点所能到的范围,再取并集就好了。

代码如下:

#include<bits/stdc++.h>
 
#define N 100010
#define LN 18
 
using namespace std;
 
struct data
{
    bool opt;
    int u;
    data(){};
    data(bool a,int b){opt=a,u=b;}
};
 
int t,n,m;
int cnt,head[N],nxt[N<<1],to[N<<1];
int fa[N][LN],d[N],size[N];
 
void init()
{
    cnt=0;
    memset(head,0,sizeof(head));
}
 
void adde(int u,int v)
{
    to[++cnt]=v;
    nxt[cnt]=head[u];
    head[u]=cnt;
}
 
void dfs(int u)
{
    size[u]=1;
    for(int i=1;i<=17;i++)
        fa[u][i]=fa[fa[u][i-1]][i-1];
    for(int i=head[u];i;i=nxt[i])
    {
        if(to[i]!=fa[u][0])
        {
            d[to[i]]=d[u]+1;
            fa[to[i]][0]=u;
            dfs(to[i]);
            size[u]+=size[to[i]];
        }
    }
}
 
int lca(int a,int b)
{
    if(d[a]<d[b])
        swap(a,b);
    for(int i=17;i>=0;i--)
        if(d[fa[a][i]]>=d[b])
            a=fa[a][i];
    if(a==b)
        return a;
    for(int i=17;i>=0;i--)
        if(fa[a][i]!=fa[b][i])
            a=fa[a][i],b=fa[b][i];
    return fa[a][0];
}
 
int up(int u,int d)
{
    for(int i=17;i>=0;i--)
        if(d>=(1<<i))
            u=fa[u][i],d-=(1<<i);
    return u;
}
 
data getmid(int x,int y,int lca)
{
    int len=d[x]+d[y]-(d[lca]<<1);
    if(d[x]>=d[y]) return data(0,up(x,(len-1)>>1));
    return data(1,up(y,len>>1));
}
 
int query(int x,int y,int z,int xy,int xz)
{
    data ans1=getmid(x,y,xy),ans2=getmid(x,z,xz);
    if(!ans1.opt&&!ans2.opt)
    {
        if(d[ans1.u]<d[ans2.u])
            swap(ans1,ans2);
        return size[ans1.u];
    }
    else
    {
        if(ans1.opt&&ans2.opt)
        {
            if(d[ans1.u]<d[ans2.u])
                swap(ans1,ans2);
            if(lca(ans1.u,ans2.u)==ans2.u)
                return n-size[ans2.u];
            return n-size[ans2.u]-size[ans1.u];
        }
        else
        {
            if(ans1.opt)
                swap(ans1,ans2);
            if(lca(ans1.u,ans2.u)==ans1.u)
                return size[ans1.u]-size[ans2.u];
            return size[ans1.u];
        }
    }
}
 
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        init();
        scanf("%d",&n);
        for(int i=1;i<n;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            adde(u,v),adde(v,u);
        }
        d[1]=1;
        dfs(1);
        scanf("%d",&m);
        while(m--)
        {
            int x,y,z;
            scanf("%d%d%d",&x,&y,&z);
            int xy=lca(x,y),xz=lca(x,z),yz=lca(y,z);
            printf("%d %d %d\n",query(x,y,z,xy,xz),query(y,x,z,xy,yz),query(z,x,y,xz,yz));
        }
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值