hdu 6031 Innumerable Ancestors lca + 二分

题目:

http://acm.hdu.edu.cn/showproblem.php?pid=6031

题意:

给定一个无向树形图,1为根,对于每次查询,给出两个集合,问两个集合中各选出一个点的最近公共祖先的深度最深为多少

思路:

用倍增求lca,然后二分枚举答案,求出第一个集合中的点在枚举的深度上的祖先,并标记这些祖先,然后求第二个集合中的点在枚举的深度上的祖先,检查两个集合在枚举深度上的祖先有没有重合,有重合就意味着在枚举深度上有lca

#include <bits/stdc++.h>
using namespace std;

const int N = 100000 + 10, INF = 0x3f3f3f3f;
struct edge
{
    int to, next;
}g[N*2];

int cnt, head[N];
int dep[N], dis[N], fat[N][20];
int x[N], y[N];
bool vis[N];

void init()
{
    cnt = 0;
    memset(head, -1, sizeof head);
}
void add_edge(int v, int u)
{
    g[cnt].to = u, g[cnt].next = head[v], head[v] = cnt++;
}
void dfs(int v, int fa, int d)
{
    dep[v] = d, fat[v][0] = fa;
    for(int i = head[v]; ~i; i = g[i].next)
    {
        int u = g[i].to;
        if(u == fa) continue;
        dfs(u, v, d+1);
    }
}
void lca_init(int n)
{
    for(int j = 1; (1<<j) <= n; j++)
        for(int i = 1; i <= n; i++)
            fat[i][j] = fat[fat[i][j-1]][j-1];
}
int lca(int v, int u)
{
    if(dep[v] < dep[u]) swap(v, u);
    int d = dep[v] - dep[u];
    for(int i = 0; (d>>i) != 0; i++)
        if((d>>i) & 1) v = fat[v][i];
    if(v == u) return v;
    for(int i = 18; i >= 0; i--)
        if(fat[v][i] != fat[u][i]) v = fat[v][i], u = fat[u][i];
    return fat[v][0];
}
int query(int v, int d)
{
    if(d < 0) return -1;
    if(d == 0) return v;
    for(int i = 0; (d>>i) != 0; i++)
        if((d>>i) & 1) v = fat[v][i];
    return v;
}
bool check(int mid, int kx, int ky)
{
    set<int> ste;
    for(int i = 1; i <= kx; i++)
    {
        int d = dep[x[i]] - mid;
        int v = query(x[i], d);
        if(v != -1) ste.insert(v);
    }
    for(int i = 1; i <= ky; i++)
    {
        int d = dep[y[i]] - mid;
        int v = query(y[i], d);
        if(ste.count(v)) return true;
    }
    return false;
}
int main()
{
    int n, m;
    while(~ scanf("%d%d", &n, &m))
    {
        init();
        int a, b;
        for(int i = 1; i <= n-1; i++)
        {
            scanf("%d%d", &a, &b);
            add_edge(a, b); add_edge(b, a);
        }
        dfs(1, 0, 1);
        lca_init(n);
        int kx, ky;
        for(int i = 1; i <= m; i++)
        {
            int mx = 0;
            scanf("%d", &kx);
            for(int j = 1; j <= kx; j++) scanf("%d", &x[j]), mx = max(mx, dep[x[j]]);
            scanf("%d", &ky);
            for(int j = 1; j <= ky; j++) scanf("%d", &y[j]);
            int l = 1, r = mx, ans;
            while(l <= r)
            {
                int mid = (l + r) >> 1;
                if(check(mid, kx, ky)) ans = mid, l = mid + 1;
                else r = mid - 1;
            }
            printf("%d\n", ans);
        }
    }
    return 0;
}

我自己写了一个挺暴力的方法,首先把两个集合中的点分别按照深度从大到小排序,然后直接两重循环暴力,一个优化是如果当前点的深度小于已经求出的lca深度,就continue,然后这样就过了。。。

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N = 100010, INF = 0x3f3f3f3f;
struct edge
{
    int to, next;
}g[N*2];
int cnt, head[N];
int dis[N];
int dp[20][N*2];
int tot, dep[N*2], ord[N*2], fir[N];
int x[N], y[N];
void init()
{
    cnt = 0;
    memset(head, -1, sizeof head);
    tot = 0;
}
void add_edge(int v, int u)
{
    g[cnt].to = u, g[cnt].next = head[v], head[v] = cnt++;
}
void dfs(int v, int fa, int d)
{
    ord[++tot] = v, dep[tot] = d, fir[v] = tot;
    for(int i = head[v]; i != -1; i = g[i].next)
    {
        int u = g[i].to;
        if(u == fa) continue;
        dfs(u, v, d + 1);
        ord[++tot] = v, dep[tot] = d;
    }
}
void ST(int n)
{
    for(int i = 1; i <= n; i++)
        dp[0][i] = i;
    for(int i = 1; (1<<i) <= n; i++)
        for(int j = 1; j <= n - (1<<i) + 1; j++)
            dp[i][j] = dep[dp[i-1][j]] < dep[dp[i-1][j+(1<<(i-1))]] ? dp[i-1][j] : dp[i-1][j+(1<<(i-1))];
}
int RMQ(int l, int r)
{
    int k = log(r - l + 1) / log(2.0);
    return dep[dp[k][l]] < dep[dp[k][r-(1<<k)+1]] ? dp[k][l] : dp[k][r-(1<<k)+1];
}
int LCA(int v, int u)
{
    v = fir[v], u = fir[u];
    if(v > u) swap(v, u);
    int res = RMQ(v, u);
    return ord[res];
}
int main()
{
    int n, m;
    while(~ scanf("%d%d", &n, &m))
    {
        init();
        int a, b;
        for(int i = 1; i <= n-1; i++)
        {
            scanf("%d%d", &a, &b);
            add_edge(a, b); add_edge(b, a);
        }
        dfs(1, 0, 1);
        ST(2*n - 1);
        int kx, ky;
        for(int i = 1; i <= m; i++)
        {
            scanf("%d", &kx);
            for(int j = 1; j <= kx; j++) scanf("%d", &x[j]);
            scanf("%d", &ky);
            for(int j = 1; j <= ky; j++) scanf("%d", &y[j]);
            sort(x + 1, x + 1 + kx, [](int a, int b){return dep[fir[a]] > dep[fir[b]];});
            sort(y + 1, y + 1 + ky, [](int a, int b){return dep[fir[a]] > dep[fir[b]];});
            int ans = 0;
            for(int j = 1; j <= kx; j++)
            {
                if(ans >= dep[fir[x[j]]]) continue;
                for(int k = 1; k <= ky; k++)
                {
                    int lca = LCA(x[j], y[k]);
                    ans = max(ans, dep[fir[lca]]);
                }
            }
            printf("%d\n", ans);
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值