祖孙询问(在线做法)
给定n个节点,m条边,输入a,b代表a,b间有个无向边,b=-1时为根,m组询问a是b的祖先时输出1,反之输出2,互不为输出0.
const int N = 40010, M = N * 2;//点数、边数最大值
int n, m;
int h[N], e[M], ne[M], idx;
int depth[N], fa[N][16];//depth代表深度 fa[i][k]代表节点i向上跳2^k次的节点编号
int q[N];
void add(int a, int b)
{ e[idx] = b;ne[idx] = h[a];h[a] = idx ++;}
void bfs(int root)//宽搜不容易因为递归层数过多爆栈
{
memset(depth,0x3f,sizeof depth);
depth[0] = 0,depth[root] = 1; queue<int> q; q.push(root);
while(q.size())
{int t = q.front(); q.pop();
for(int i=h[t];i!=-1;i=ne[i])
{ int j = e[i];
if(depth[j]>depth[t]+1)//说明j还没被搜索过
{
depth[j] = depth[t]+1;q.push(j);fa[j][0] = t;//把第depth[j]层的j加进队列j往上跳2^0=1步后就是父节点t
for(int k=1;k<=15;k++)//从小到大往上跳递推
fa[j][k] = fa[fa[j][k-1]][k-1];
}}}}
int lca(int a, int b)//返回a、b公共祖先
{
// 为方便处理 当a在b上面时 把a b 互换
if (depth[a] < depth[b]) swap(a, b); //把深度更深的a往上跳到b
for (int k = 15; k >= 0; k -- ) //当a跳完2^k依然在b下面 我们就一直跳
if (depth[fa[a][k]] >= depth[b]) a = fa[a][k];
if (a == b) return a; //a,b同层但不同节点
for (int k = 15; k >= 0; k -- )
if (fa[a][k] != fa[b][k])
{ a = fa[a][k]; b = fa[b][k];}
return fa[a][0]; //lca(a,b) = 再往上跳1步即可
}
int getdis(int x, int y)
{
return depth[x] + depth[y] - 2 * depth[lca(x, y)];
}
int main()
{
cin >> n;int root = 0; memset(h, -1, sizeof h);
for (int i = 0; i < n; i ++ )
{ int a, b;cin >> a >> b;if (b == -1) root = a; else add(a, b), add(b, a);}
bfs(root); scanf("%d", &m);
while (m -- )
{
int a, b; cin >> a >> b;
int p = lca(a, b) if (p == a) cout << "1" << endl; else if (p == b) cout << "2" << endl; else cout << "0" << endl;
}
}
离线LCA O(N+M)
给出 n 个点的一棵树,多次询问两点之间的最短距离。两个节点到根的距离-两倍根节点到最先公共祖先的距离。
const int N = 10010, M = N * 2;
int n, m, h[N], e[M], w[M], ne[M], idx,dist[N],p[N],res[M],st[N];
//dist[i]为i节点到根节点的距离 p[i]并查集 res[i]第i次询问的答案,st[i]第i个点是否访问
vector<PII> query[N];//把询问存下来
// query[i][first][second] first存查询距离i的另外一个点j,second存查询编号idx
void add(int a,int b,int c)
e[idx] = b; ne[idx] = h[a];w[idx] = c;h[a] = idx++;
int find(int x)
if(p[x]!=x)p[x] = find(p[x]); return p[x];
void dfs(int u,int fa)
for(int i=h[u];~i;i=ne[i])
{int j = e[i];if(j==fa) continue;dist[j] = dist[u]+w[i]; dfs(j,u);}
void tarjan(int u)
{ //2号点:代表已经访问并结束回溯1号点:代表正在访问0号点:代表还没有访问过其中所有2号点和正在搜索的1号点路径中已经通过并查集合并成一个集合
st[u]=1;//当前路径点标记为1 u这条路上的根节点的左下的点用并查集合并到根节点
for(int i = h[u];~i;i=ne[i])
{ int j = e[i]; if(!st[j]){ tarjan(j);p[j] = u;}//从左下回溯后把左下的点合并到根节点}
// 对于当前点u 搜索所有和u
for(auto item:query[u])
{
int y = item.first,id = item.second;
if(st[y]==2)//如果查询的这个点已经是左下的点(已经搜索过且回溯过,标记为2)
{ int anc = find(y);//y的根节点 // x到y的距离 = d[x]+d[y] - 2*d[lca]
res[id] = dist[u]+dist[y] - dist[anc]*2;//第idx次查询的结果 res[idx]
}
} st[u] = 2; //点u已经搜索完且要回溯了 就把st[u]标记为2
}
int main()
{
cin >> n >> m; memset(h,-1,sizeof h);
for(int i=0;i<n-1;i++)
int a,b,c; cin >> a >> b >> c;add(a,b,c),add(b,a,c);
// 存下询问
for(int i=0;i<m;i++)
{
int a,b; cin >> a >> b;
if(a!=b) query[a].push_back({b,i}),query[b].push_back({a,i});
}
for(int i=1;i<=n;i++)p[i] = i;
dfs(1,-1); tarjan(1); for(int i=0;i<m;i++)cout << res[i] << '\n';//把每次询问的答案输出
}