题目:
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;
}