HDU6031[Innumerable Ancestor]--二分+倍增思想

Ps:博主吐槽一下,那个多组数据要了博主半条命,白拍了半天啊。QAQ(大佬可无视)

【链接】
HDU6031

【题目大意】
给你一个n个节点的树,然后给你一个集合A和一个集合B,两个集合中分别有一些树的节点编号。让你从这两个集合中选出两个数x和y,使x,y的LCA的深度最大。

【解题报告】
从题目上我们可以想到用二分,但是二分分什么呢?分点与祖先的距离?好像不太行,时间复杂度太大。我们可以假设x,y的LCA为k,那么对于k的祖先节点都是x,y的公共祖先且这些点的深度小于k的深度,所以我们可以二分深度mid,求出A,B集合中的点的祖先深度是mid的,然后看这两个集合是否有交集。求祖先时运用倍增思想预先构造处fa数组,然后此题就解决了。

#include<cstdio>
#include<cstring>
#include<set>
using namespace std;
const int maxn=100005,maxm=200005,maxv=18;
int n,m,tot,L,R,len_a,len_b,MAX,a[maxn],b[maxn],dep[maxn],lnk[maxn],son[maxm],nxt[maxm],fa[maxn][maxv];
bool vis[maxn];
inline int Read()
{
    int res=0;
    char ch=getchar();
    while (ch<'0'||ch>'9') ch=getchar();
    while (ch>='0'&&ch<='9') res=res*10+ch-48,ch=getchar();
    return res;
}
void Add(int x,int y)
{
    son[++tot]=y; nxt[tot]=lnk[x]; lnk[x]=tot;
}
void Dfs(int x)
{
    dep[x]=dep[fa[x][0]]+1; MAX=max(MAX,dep[x]);
    for (int j=lnk[x]; j; j=nxt[j])
     if (fa[x][0]!=son[j]) fa[son[j]][0]=x,Dfs(son[j]);
}
bool Check(int x)
{
    set <int> S; S.clear();//这里用set判断交集比较方便简单
    for (int i=1; i<=len_a; i++)
    {
        int d=dep[a[i]]-x,now=a[i];
        if (d<0) continue;
        for (int j=maxv-1; j>=0; j--)
         if (d-(1<<j)>=0) now=fa[now][j],d-=1<<j;
        if (now<0) continue;
        S.insert(now);
    }
    for (int i=1; i<=len_b; i++)
    {
        int d=dep[b[i]]-x,now=b[i];
        for (int j=maxv-1; j>=0; j--)
         if (d-(1<<j)>=0) now=fa[now][j],d-=1<<j;
        if (S.count(now)) return 1;
    }
    return 0;
}
void Work()
{
    tot=MAX=0;
    memset(lnk,0,sizeof(lnk));
    for (int i=1,x,y; i<n; i++) x=Read(),y=Read(),Add(x,y),Add(y,x);
    memset(fa,255,sizeof(fa));
    Dfs(1);
    for (int j=1; j<maxv; j++)
     for (int i=1; i<=n; i++)
      if (fa[i][j-1]!=-1) fa[i][j]=fa[fa[i][j-1]][j-1];
    for (int i=1; i<=m; i++)
    {
        len_a=Read();
        for (int j=1; j<=len_a; j++) a[j]=Read();
        len_b=Read();
        for (int j=1; j<=len_b; j++) b[j]=Read();
        L=1; R=MAX;
        while (L<=R)
        {
            int mid=(R-L>>1)+L;
            if (Check(mid)) L=mid+1; else R=mid-1;
        }
        printf("%d\n",R);
    }
}
int main()
{
    freopen("6031.in","r",stdin);
    freopen("6031.out","w",stdout);
    while (scanf("%d%d",&n,&m)==2) Work();
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值