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