LCA__st算法&&树上倍增
st表
#include<cstdio> #include<algorithm> #include<cmath> using namespace std; int go[2000005]; int nxt[2000005]; int adj[2000005]; int pos[2000005]; int st[2000005][22]; int dep[2000005]; int aa[2000005]; int lg[2000005]; int ecnt,cnt,m,n,s,x,y,a,b; void add(int u,int v) { //建链前 go[++ecnt]=v; nxt[ecnt]=adj[u]; adj[u]=ecnt; } void dfs(int u,int pre) { dep[u]=dep[pre]+1; //首先预处理出深度 aa[++cnt]=u; //DFS序 pos[u]=cnt; //第几个出现的 for(int e=adj[u]; e; e=nxt[e]) { if(go[e]!=pre) { dfs(go[e],u); aa[++cnt]=u; } } } int MIN(int p,int q) { if(dep[p]<dep[q]) return p; else return q; } void build() { for(int i=1; i<=cnt; i++) { st[i][0]=aa[i]; } for(int i = 1, j = 0; i <= cnt; i++){ if(1 << (j + 1) == i) j++; lg[i] = j; } for(int j=1; j<=lg[cnt]; j++) { for(int i=1; i + (1 << j) - 1 <=cnt; i++) { st[i][j]=MIN(st[i][j-1],st[i+(1 << (j - 1))][j-1]); } } } /* 读入两个节点,查询它们第一次出现的位置 在这两个位置之间的区间查询最小深度的节点,该节点即为最近公共祖先 */ int lca(int l,int r) { int xx=pos[l]; int yy=pos[r]; if(xx>yy) swap(xx,yy); int k=lg[yy-xx+1]; return MIN(st[xx][k],st[yy-(1 << k)+1][k]); } template <class T> //快速读入 void read(T &x){ char c; bool op = 0; while(c = getchar(), c < '0' || c > '9') if(c == '-') op = 1; x = c - '0'; while(c = getchar(), c >= '0' && c <= '9') x = x * 10 + c - '0'; if(op) x = -x; } template <class T> //快速输出 void write(T x){ if(x < 0) putchar('-'), x = -x; if(x >= 10) write(x / 10); putchar('0' + x % 10); } int main() { //scanf("%d%d%d",&n,&m,&s); read(n), read(m), read(s); for(int i=1; i<=n-1; i++) { //scanf("%d%d",&x,&y); read(x), read(y); add(x,y); add(y,x); } dfs(s,0); build(); for(int i=1; i<=m; i++) { //scanf("%d%d",&a,&b); //printf("%d\n",lca(a,b)); read(a), read(b); write(lca(a, b)), putchar('\n'); } return 0; }
树上倍增和st表的思路一样,只是实现方法不同
/* 倍增求LCA: father【i】【j】表示节点i往上跳2^j次后的节点 可以转移为 father【i】【j】=father【father【i】【j-1】】【j-1】 (此处注意循环时先循环j,再循环i) 然后dfs求出各个点的深度depth 整体思路: 先比较两个点的深度,如果深度不同,先让深的点往上跳,浅的先不动, 等两个点深度一样时,if 相同 直接返回,if 不同 进行下一步;如果不同 ,两个点一起跳,j从大到小枚举(其实并不大),如果两个点都跳这么多后 ,得到的点相等,两个点都不动(因为有可能正好是LCA也有可能在LCA上方) ,知道得到的点不同,就可以跳上来,然后不断跳,两个点都在LCA下面那层, 所以再跳1步即可,当father【i】【j】中j=0时即可,就是LCA,返回值结束 */ #include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<queue> using namespace std; vector <int> g[100010]; int father[100010][40]={0}; int depth[100010]={0}; int n,m; bool visit[10010]={false}; int root; void dfs(int u)//深搜出各点的深度,存在depth中 { int i; visit[u]=true; for (i=0;i<g[u].size();i++) { int v=g[u][i]; if ( !visit[v] ) { depth[v]=depth[u]+1; dfs(v); } } } void bz()//fa数组的预处理 { int i,j; for (j=1;j<=30;j++) for (i=1;i<=n;i++) father[i][j]=father[father[i][j-1]][j-1]; }//倍增,处理father数组,详情参照上述讲解 int LCA(int u,int v) { if ( depth[u]<depth[v] ) { int temp=u; u=v; v=temp; }//保证深度大的点为u,方便操作 int dc=depth[u]-depth[v]; int i; for (i=0;i<30;i++)//值得注意的是,这里需要从零枚举 { if ( (1<<i) & dc)//从大到小二分 u=father[u][i];//意思是跳2^j步不一样,就跳,否则不跳 } //上述操作先处理较深的结点,使两点深度一致 if (u==v) return u;//如果深度一样时,两个点相同,直接返回 for (i=29;i>=0;i--)//从大到小二分。 { if (father[u][i]!=father[v][i])//跳2^j步不一样,就跳,否则不跳 { u=father[u][i]; v=father[v][i]; } } u=father[u][0];//上述过程做完,两点都在LCA下一层,所以走一步即可 return u; } int main() { int i,j; scanf("%d",&n); for (i=0;i<=n;i++) g[i].clear(); for (i=1;i<n;i++) { int a,b; int root; scanf("%d%d",&a,&b); g[a].push_back(b); father[b][0]=a;//a^0为1,所以fa[a][0]代表a向上走一格 if (father[a][0]==0)//如果节点的根为0,证明该节点为根节点 root = a; } depth[root]=1; dfs(root); bz(); int x,y; scanf("%d%d",&x,&y); printf("%d",LCA(x,y)); return 0; }
只想找一个不会伤害我的人