题目链接:http://cstest.scu.edu.cn/soj/problem.action?id=1187
题目大意:
LCA模板题
算法:
我写LCA从来都是用倍增,倍增是在线的,但是传说比离线的tarjan慢1/3。
倍增依据的思想是ST算法,附一段网上的介绍:
dp[i][j]表示区间[i,i+2^j-1]的最小值,则
dp[i][0]=a[i]
dp[i][j]=min{dp[i][j-1],dp[i+2^(j-1)][j-1]}
这样可以得到所有的dp[i][j]
如果询问区间为[s,t],则只需要取k=(int)log2(t-s+1)
RMQ[s,t]=min{dp[s][k],dp[t-2^k+1][k]}
这时候预处理的算法复杂度仅为O(nlogn)
而回答问题仍然是O(1)的复杂度
实际操作的时候,我们不一定用log来计算k,也可以通过二分查找。
在倍增法求LCA的过程中,
go[i][j]数组表示由i点向上跳j层到达的根节点。
预处理go数组,可以dfs的时候由根节点向子节点传递一下。
求解LCA时,先让两个点位于同一层上,
然后再二分地让他们跳到LCA下面一层的位置,就可以求出LCA。
代码如下:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
int go[11000][17],dep[11000],hash[11000],p[11000],dis[11000][17];
vector<int>map[11000];
void dfs(int u)
{
for(int t=0; t<map[u].size(); t++)
{
int v=map[u][t];
dep[v]=dep[u]+1;
go[v][0]=u;
for(int k=1; (1<<k)<=dep[v]; k++)
{
go[v][k]=go[go[v][k-1]][k-1];
}
dfs(v);
}
}
int LCA(int u,int v)
{
if(dep[v]>dep[u])
swap(u,v);
int jmp=dep[u]-dep[v];
for(int k=16; k>=0; k--)
if(jmp>>k&1)
{
u=go[u][k];
}
if(u==v)
return u;
int k;
for(k=16; k>=0; k--)
if(((1<<k)<dep[u])&&go[u][k]!=go[v][k])
{
u=go[u][k];
v=go[v][k];
}
return go[u][0];
}
int main()
{
int n,m;
while(~scanf("%d",&n))
{
memset(p,-1,sizeof(p));
memset(go,-1,sizeof(go));
for(int i=0; i<n; i++)
{
int u,v,num;
scanf("%d:(%d)",&u,&num);
u--;
map[u].clear();
while(num--)
{
scanf("%d",&v);
v--;
map[u].push_back(v);
p[v]=u;
}
}
for(int i=0; i<n; i++)
{
if(p[i]==-1)
{
dep[i]=0;
dfs(i);
}
}
memset(hash,0,sizeof(hash));
scanf("%d\n",&m);
while(m--)
{
int u,v;
scanf("(%d,%d)",&u,&v);
getchar();
u--;
v--;
hash[LCA(u,v)]++;
}
for(int i=0;i<n;i++)
{
if(hash[i])
{
printf("%d:%d\n",i+1,hash[i]);
}
}
}
return 0;
}