3 solutions

  • 3
    @ 2025-1-16 21:34:38

    DFS序求LCA的模板。注释就不写了,认真上了课的人应该都能看懂。

    这个写法涉及了两个技巧。

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N = 500010;
    int n, m, s;
    int tot, dfn[N], st[20][N], Log2[N];
    vector<int> g[N];
    
    void build() {
    	int u, v;
    	for (int i = 1; i <= n - 1; i++) {
    		cin >> u >> v;
    		g[u].emplace_back(v);
    		g[v].emplace_back(u);
    	}
    }
    
    void dfs(int u, int fa) {
    	dfn[u] = ++tot;
    	st[0][dfn[u]] = fa;
    	for (auto v : g[u]) {
    		if (v == fa) continue;
    		dfs(v, u);
    	}
    }
    
    inline int get(int x, int y) {
    	return dfn[x] < dfn[y] ? x : y;
    }
    
    void init() {
    	Log2[1] = 0;
    	for (int i = 2; i <= n; i++)
    		Log2[i] = Log2[i >> 1] + 1;
    
    	for (int j = 1; j <= 18; j++)
    		for (int i = 1; i + (1 << j) - 1 <= n; i++)
    			st[j][i] = get(st[j - 1][i], st[j - 1][i + (1 << (j - 1))]);
    }
    
    int lca(int u, int v) {
    	if (u == v) return u;
    	u = dfn[u], v = dfn[v];
    	if (u > v) swap(u, v);
    	int k = Log2[v - u];
    	// 查询区间 [dfn(u)+1, dfn(v)] 内dfn最小的节点编号
    	return get(st[k][u + 1], st[k][v - (1 << k) + 1]);
    }
    
    int main() {
    	ios::sync_with_stdio(0);
    	cin.tie(0);
    	cin >> n >> m >> s;
    	build();
    	dfs(s, 0);
    	init();
    	int u, v;
    	while (m--) {
    		cin >> u >> v;
    		cout << lca(u, v) << '\n';
    	}
    
    	return 0;
    }
    
    • 2
      @ 2025-1-20 16:41:43

      司陆

      DFS序:用DFS遍历整棵树遍历的先后顺序
      时间戳:每个点被遍历到的时间
      此时,我们又会发现,如果有如下一棵树: 不是不会有人打不开吧...... 【如果打不开,请见附录】
      设lca(u,v)为W

      数组 1 2 3 4 5 6 7 8
      存储顺序 W B vi u D C E v
      深度 0 1 2 3 4
      DFS序 W B u C vi D E v
      时间戳(dfn) 1 2 5 3 6 4 7 8

      我们会发现,在DFS序中,u到v中深度最小的那个点的父亲就是lca(u,v)。
      由此,我们可以创建一个ST表,在st[0][i]存入i的父亲,并让st表以DFS序的顺序存储。由于两个点的深度差和他们父亲的深度差是一样的(父亲的深度都比自己少1),因此,这种操作即是正确的,也省了一个数组。再让数组存储当前区间深度最小的那个点的编号,就可以非常快~~~~的求出LCA了。
      可是,代码还可以继续优化。可不可以把比较依据从深度改成时间戳呢?看图可以发现,lca(u,v)是以其为根的子树的节点中时间戳最小的,若设一个点vi为lca(u,v)的、在v到lca(u,v)上的最小的、lca(u,v)的儿子,又由于st表存的是自己的父亲,所以st[0][dfn[vi]]的时间戳是最小的。(CCF语言???) 因此,以时间戳为比较依据是可以的。

      呆码

      #include<bits/stdc++.h>
      using namespace std;
      typedef long long ll;
      ll st[500001][21];
      vector<ll>edge[500001];
      ll dfn[500001];
      ll n,m,s,tot;
      ll lg[500001];
      ll get(ll a,ll b){
      	return dfn[a]<dfn[b]?a:b;
      }
      void dfs(ll num,ll p){
      	dfn[num]=++tot;
      	st[dfn[num]][0]=p;
      	for(auto i:edge[num]){
      		if(i==p)continue;
      		dfs(i,num);
      	}
      }
      void init(){
      	for(ll i=2;i<=500000;i++){
      		lg[i]=lg[i>>1]+1;
      	}
      	dfs(s,0);
      	for(ll i=1;i<=18;i++){
      		for(ll j=1;j+(1<<i)-1<=n;j++){
      			st[j][i]=get(st[j][i-1],st[j+(1<<(i-1))][i-1]);//以dfn为判断依据
      		}
      	}
      }
      ll lca(ll u,ll v){
      	if(u==v)return u;
      	u=dfn[u],v=dfn[v];
      	if(u>v)swap(u,v);
      	ll k=lg[v-u];
      //	cout<<u<<" "<<v<<" "<<k<<"\n";
      //	cout<<st[u+1][k]<<" "<<st[v-(1<<k)+1][k]<<"\n\n";
      	return get(st[u+1][k],st[v-(1<<k)+1][k]);
      }
      int main(){
      	ios::sync_with_stdio(false);
      	cin.tie(0);cout.tie(0);
      	cin>>n>>m>>s;
      	ll u,v;
      	for(ll i=1;i<n;i++){
      		cin>>u>>v;
      		edge[u].emplace_back(v);
      		edge[v].emplace_back(u);
      	}
      	init();
      //	for(ll i=0;i<=5;i++){
      //		for(ll j=1;j<=n;j++){
      //			cout<<st[j][i]<<" ";
      //		}
      //		cout<<'\n';
      //	}
      	// for(ll i=1;i<=n;i++)cout<<dfn[i]<<" ";
      	for(ll i=1;i<=m;i++){
      		cin>>u>>v;
      		cout<<lca(u,v)<<"\n";
      	}
      }
      

      附录

      1.图片打不开怎么办?
      A:打开Graph Editor,复制以下内容到左侧的Graph Data,再调整一下即可(注意根节点为lca(u,v)):

      lca(u,v) B
      B u
      C u
      D vi
      lca(u,v) vi
      E D
      E v
      

      A2:如果非常可惜,你的电脑连不了网络(bushi),以上的数据也可以自己手绘。每行说明一条边,每行两个参数为两个端点。
      若有错误,欢迎提出。

      • 2
        @ 2025-1-16 11:40:50

        发个倍增解法的简单版题解,主要是提一下代码中的几个小细节。请看代码注释。

        #include <bits/stdc++.h>
        using namespace std;
        
        const int N = 500010;
        int n, m, s;
        int anc[N][20], d[N];
        vector<int> g[N];
        
        void build() {
        	int u, v;
        	for (int i = 1; i <= n - 1; i++) {
        		cin >> u >> v;
        		g[u].emplace_back(v);
        		g[v].emplace_back(u);
        	}
        }
        
        void dfs(int u, int fa) {
        	for (auto v : g[u]) {
        		if (v == fa) continue;
        		d[v] = d[u] + 1;
        		anc[v][0] = u;
        		dfs(v, u);
        	}
        }
        
        void init() {
        	for (int j = 1; j <= 18; j++)
        		for (int i = 1; i <= n; i++)
        			anc[i][j] = anc[anc[i][j - 1]][j - 1];
        }
        
        int lca(int u, int v) {
        	if (d[u] < d[v]) swap(u, v);
        	for (int i = 18; i >= 0; i--)
        		// 注意这里是拿u的祖先跟v比,所以要取等号
        		if (d[anc[u][i]] >= d[v])
        			u = anc[u][i];
        	if (u == v) return u;
        	for (int i = 18; i >= 0; i--)
        		if (anc[u][i] != anc[v][i])
        			u = anc[u][i], v = anc[v][i];
        	return anc[u][0]; // 注意返回的是u的父节点
        }
        
        int main() {
        	ios::sync_with_stdio(0);
        	cin.tie(0);
        	cin >> n >> m >> s;
        	build();
        	// 重要细节!!!d[s]不能取0,因为对于无法往上跳 2^j 步的情况,
        	// 用编号0来表示,而这些情况对应的d也是0(初始化)。
        	d[s] = 1;
        	dfs(s, 0);
        	init();
        	int u, v;
        	while (m--) {
        		cin >> u >> v;
        		cout << lca(u, v) << '\n';
        	}
        
        	return 0;
        }
        
        • @ 2025-1-16 19:59:14
          #include<iostream>
          #include<vector>
          #define N 500005
          using namespace std;
          int n,m,s;
          vector<int>g[N];
          int anc[N][20];
          int deep[N];
          void init()
          {
          	for(int j=1;j<=18;j++)
          		for(int i=1;i<=n;i++)
          			anc[i][j]=anc[anc[i][j-1]][j-1];
          }
          void dfs(int u,int fa)
          {
          	for(auto v:g[u])
          	{
          		if(v==fa)continue;
          		deep[v]=deep[u]+1;
          		anc[v][0]=u;
          		dfs(v,u);
          	}
          }
          int lca(int x,int y)
          {
          	if(deep[x]<deep[y])swap(x,y);
          	for(int i=18;i>=0;i--)//从18->0 
          		if(deep[anc[x][i]]>=deep[y])
          			x=anc[x][i];
          	if(x==y)return x;//判断是否是同一个点 
          	for(int i=18;i>=0;i--)//跳 
          		if(anc[x][i]!=anc[y][i])
          			x=anc[x][i],y=anc[y][i];
          	return anc[x][0];
          }
          int main()
          {
          	ios::sync_with_stdio(0);
          	cin.tie(0);
          	cin>>n>>m>>s;
          	for(int i=1;i<n;i++)
          	{
          		int u,v;cin>>u>>v;
          		g[u].push_back(v);
          		g[v].push_back(u);
          	}
          	deep[s]=1;
          	dfs(s,0);
          	init();
          	while(m--)
          	{
          		int x,y;cin>>x>>y;
          		cout<<lca(x,y)<<"\n";
          	}
          	return 0;
          }
          
        • @ 2025-1-17 13:58:44

          不是原来int真的比long long快耶,调成int直接就A了……

      • 1

      Information

      ID
      318
      Time
      2000ms
      Memory
      512MiB
      Difficulty
      8
      Tags
      # Submissions
      132
      Accepted
      16
      Uploaded By