読者です 読者をやめる 読者になる 読者になる

srupのメモ帳

競プロで解いた問題や勉強したことを記録していくメモ帳

ABC 014 D - 閉路

問題

問題概要

木のグラフが与えられる。その木に対して、辺を1つ加えでできる、閉路の長さを答える。

解法

まず、LCAを求める。LCAについては、蟻本。多くのクエリが与えられるので、いちいち親をたどっていては、間に合わない。そこで、頂点vから、2k回上に廻った頂点を記録しておく。parent[k][v] := vから2k回上に廻ったときの頂点とおき、前計算で計算しておく。この計算はダブリングで高速に求めることができる。

ミス

LCAを利用するのね。

コード

#include <iostream>
#include <vector>
#include <cstdio>
using namespace std;
#define rep(i,n) for(int i=0;i<(n);i++)

class Tree_lca{
public:
    Tree_lca(int V, int root) : V(V), root(root){
        G.resize(V);
        for (int i = 0; i < MAXLOG_V; ++i) parent[i].resize(V);
        depth.resize(V);
    }

    //u-vをunite
    void unite(int u, int v){
        G[u].push_back(v);
        G[v].push_back(u);
    }

    void init(){
        //parent[0]とdepthを初期化
        dfs(root, -1, 0);
        //parentを初期化 2^kごとのテーブルを作成
        for (int k = 0; k + 1 < MAXLOG_V; ++k){
            for (int v = 0; v < V; ++v){
                if(parent[k][v] < 0) parent[k + 1][v] = -1;//root(rootより上)が親
                else parent[k + 1][v] = parent[k][parent[k][v]];//ダブリングで2倍上の親を求める
            }
        }
    }

    //u, vのLCAを求める
    int lca(int u, int v){
        //uとvが同じ深さになるまで親を辿る
        if (depth[u] > depth[v]) swap(u, v);
        for (int k = 0; k + 1 < MAXLOG_V; k++){
            //uがvと同じ高さになるまで親を辿る
            if (((depth[v] - depth[u]) >> k) & 1) {
                v = parent[k][v];//bitが立っていれば、親を2^k辿っていく
            }
        }
        if (u == v) return u;
        //2分探索でLCAを求める LCAより上の親(LA)は常に同じになる
        for (int k = MAXLOG_V - 1; k >= 0; k--) {
            if (parent[k][u] != parent[k][v]){//LCAにたどり着く限界まで、上を目指せばいい
                u = parent[k][u];
                v = parent[k][v];
            }
        }
        return parent[0][u];//1つ上がLCA
    }

    //1つ上の親と深さを設定
    void dfs(int v, int p, int d){//ノード番号 親のノード番号 深さ
        parent[0][v] = p;
        depth[v] = d;
        for(auto u : G[v]){
            if(u != p) dfs(u, v, d + 1);
        }
    }

    // uとvの距離をlcaを利用して求める
    int dist(int u, int v){
        int p = lca(u, v);
        //u-vの距離 
        return (depth[u] - depth[p]) + (depth[v] - depth[p]);
    }
    static const int MAXLOG_V = 25;
    vector<vector<int> > G;//隣接リスト
    int V, root;//頂点数と根
    vector<int> parent[MAXLOG_V];//親を2^k辿って到達する頂点(通り過ぎると-1)
    vector<int> depth;//根からの深さ
};


int main(void){
    int n; scanf("%d", &n);
    Tree_lca tree(n, 0);//頂点数 rootの頂点番号
    //unite
    rep(i, n - 1){
        int x, y; scanf("%d %d", &x, &y);
        x--; y--;
        tree.unite(x, y);
    }
    //初期化   
    tree.init();

    int q; scanf("%d", &q);
    rep(i, q){
        int a, b; scanf("%d %d", &a, &b);
        a--; b--;
        printf("%d\n", tree.dist(a, b) + 1);
    }
    return 0;
}
/*
閉路の長さを求める。最小共通祖先(LCA)を利用するといい
LCA -2つの頂点の共通の祖先(親を巡って辿り着ける頂点)
それぞれの頂点について、2^k個前の親を予め計算しておく(テーブルを作る)
テーブルの作り方
    ex)2回親を巡った時に到達する頂点parent2[v]=parent[parent[v]]
    ex)4回親を巡った時に到達する頂点parent4[v]=parent2[parent2[v]](上の情報をつかって)
    parent2^(k+1)[v]=parent2^k[parent2^k[v]] (kが小さい場合から全頂点について逐次計算していけばもとまる)
    ある頂点xの2^(k+1)個前の親 = (xの2^k個前の親)の2^k個前の親
*/

#include <iostream>
#include <algorithm>
#include <vector>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
using namespace std;
typedef long long ll;
typedef pair<int,int> pint;
typedef vector<int> vint;
typedef vector<pint> vpint;
#define mp make_pair
#define fi first
#define se second
#define all(v) (v).begin(),(v).end()
#define rep(i,n) for(int i=0;i<(n);i++)
#define reps(i,f,n) for(int i=(f);i<(n);i++)
 
class Tree_lca {
public:
    //コンストラクタ
    Tree_lca(int V, int root) : V(V), root(root) {
        //resuze(V)はVの大きさ分メモリ領域を確保することで高速化
        T.resize(V);
        for (int i = 0; i < MAXLOGV; i++) parent[i].resize(V);
        depth.resize(V);
    }
 
    // uとvをつなぐ
    // lcaを求めることが主目的なので無向グラフとしている
    void unite(int u, int v) {
        T[u].push_back(v);
        T[v].push_back(u);
    }
 
    // initする
    // コンストラクタだけじゃなくてこれも呼ばないとlcaが求められないぞ
    //dfs+2^k祖先を計算
    void init() {
        //深さと親を設定
        dfs(root, -1, 0);
        //2^kずつのテーブルを作る
        for (int k = 0; k+1 < MAXLOGV; k++) {
            for (int v = 0; v < V; v++) {
                //rootより上(存在しないところ)には-1
                if (parent[k][v] < 0) parent[k+1][v] = -1;
                //dpで親を計算していくテーブルを作成
                else parent[k+1][v] = parent[k][parent[k][v]];
            }
        }
    }
 
    // uとvのlcaを求める
    int lca(int u, int v) const {
        if (depth[u] > depth[v]) swap(u, v);
        for (int k = 0; k < MAXLOGV; k++) {
            if ((depth[v] - depth[u])>>k & 1) {
                v = parent[k][v];
            }
        }
        if (u == v) return u;
        for (int k = MAXLOGV-1; k >= 0; k--) {
            if (parent[k][u] != parent[k][v]) {
                u = parent[k][u];
                v = parent[k][v];
            }
        }
        return parent[0][u];
    }
 
    // uとvの距離をlcaを利用して求める
    // edgeを定義しないといけない時はこれじゃダメ
    int dist(int u, int v) const {
        int p = lca(u, v);
        //答えはuの深さ + bの深さ - aとbのLCAの深さ + 1
        return (depth[u]-depth[p]) + (depth[v]-depth[p]);
    }
 
    //深さと親を確認
    void dfs(int v, int p, int d) {//root,-1,0
        parent[0][v] = p;
        depth[v] = d;
        for (int next : T[v]) {
            if (next != p) dfs(next, v, d + 1);
        }
    }
 
    static const int MAXLOGV = 25;
    // グラフの隣接リスト表現
    vector<vector<int> > T;
    // 頂点の数
    int V;
    // 根ノードの番号
    int root;
 
    // 親ノード
    vector<int> parent[MAXLOGV];
    // 根からの深さ
    vector<int> depth;
};
 
int main(void){
    int n; scanf("%d", &n);
 
    //頂点の数をn 根ノードの番号を0として呼び出す
    Tree_lca tree(n, 0);
    //uniteする
    rep(i, n - 1){
        int x, y;
        scanf("%d %d", &x, &y);
        x--; y--;//0オリジンに
        tree.unite(x, y);
    }
    //inittする
    tree.init();
 
    int Q; scanf("%d", &Q);
    rep(i, Q){
        int a, b;
        scanf("%d %d", &a, &b);
        a--; b--;
        //答えは
        printf("%d\n", tree.dist(a, b) + 1);
    }
    return 0;
}