0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Atcoder解いてみた AtCoder Beginner Contest D - 閉路

0
Posted at

AtCoder Beginner Contest D - 閉路

問題はこちら

回答

クエリによって結ばれる2点のLCAを求め、2点とLCAによって構成される三角形を構成する頂点数をカウントする
しかし、素直にやると(QN)でTLE

解説では、以下のように解いていた
DFSで木を構成する各点の親、深さを求める
ダブリングを用いて、LCAを求める
ダブリング表を作った後のLCA算出の際、何回ダブリング表を用いて木をたどればいいかわからないため、LCAを越さないギリギリのところまで移動するための実装の工夫がとても勉強になる

計算量だが、
DFSにO(N)
ダブリング表作成にO(N logN)
クエリの度にダブリング表を持ちてたどるのにO(Q logN)
なため、計算量はO((Q+N)log(N))

#include <iostream>
#include <string>
#include <map>
#include <unordered_map>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
#include <queue>
#include <stack>
#include <limits.h>
#include <bitset>
#include <list>
#include <set>
#include <numeric>
#include <tuple>

int N, Q;

std::vector<std::vector<int>> G;
std::vector<int> depth;
std::vector<int> parent;

void dfs(const int& u, int d) {
	depth[u] = d;
	for (const int & v : G[u]) {
		if (depth[v] != -1) {
			continue;
		}

		parent[v] = u;
		dfs(v, d+1);
	}
}

int main()
{
	std::cin.tie(0);
	std::ios::sync_with_stdio(false);

	std::cin >> N;
	
	// 頂点1を根として、木を構築する
	G.resize(N);
	depth.resize(N, -1);
	parent.resize(N);

	for (int i = 0; i < N-1; i++) {
		int x, y;
		std::cin >> x >> y;
		x--;
		y--;

		G[x].push_back(y);
		G[y].push_back(x);
	}

	// DFSで最短距離(深さ)と最短距離を求める
	int s = 0;
	int d = 0;
	dfs(s, d);

	// ダブリング
	// parentが1回移動した先

	// 移動回数K <= 10^6程度まで対応
	const int log = 20;

	// doubling[k][v] : 頂点vから2^k回移動した先の頂点
	std::vector<std::vector<int>> doubling(log, std::vector<int>(N));

	for (int v = 0; v < N; v++) {
		// 1回移動した先
		doubling[0][v] = parent[v];
	}


	// 2^k回移動
	// = 2^(k-1)回移動後、さらに2^(k-1)回移動
	for (int k = 1; k < log; k++) {
		for (int v = 0; v < N; v++) {
			doubling[k][v] = doubling[k - 1][doubling[k - 1][v]];
		}
	}

	std::cin >> Q;

	std::vector<int> vecAns;
	for (int q = 0; q < Q; q++) {
		int a, b;
		std::cin >> a >> b;
		a--;
		b--;

		int oa = a;
		int ob = b;

		// a,bの深さをそろえる
		int* adj = nullptr;
		int adjNum = std::abs(depth[a] - depth[b]);
		if (depth[a] > depth[b]) {
			adj = &a;
		}
		if (depth[a] < depth[b]) {
			adj = &b;
		}

		// 高さをそろえる
		if (adj != nullptr) {
			for (int k = 0; k < log; k++) {
				if ((adjNum >> k) & 1LL) {
					*adj = doubling[k][*adj];
				}
			}
		}

		// a,bからそれぞれ根までさかのぼり、LCAを求める
		int lca;
		if (a == b) {
			lca = a;
		}
		else {

			// 以下の実装だと、lca飛び越えて、根まで行ってしまう可能性がある
			// →lcaの直下まで飛ぶように工夫する
			//for (int k = 0; k < log; k++) {
			//	if ((N >> k) & 1LL) {
			//		a = doubling[k][a];
			//		b = doubling[k][b];

			//		if (a == b) {
			//			lca = a;
			//			break;
			//		}
			//	}
			//}

			// 大きいジャンプから試す
			// 以下のコードにより、大きく飛びすぎてLCAより上の同じノードにたどりついても更新されない
			// a,bがLCA以下の限り位置が更新され、最終的にa,bはLCA直下に至る
			for (int k = log - 1; k >= 0; k--) {

				// 2^k上に行ってもまだ別なら上がる
				if (doubling[k][a] != doubling[k][b]) {
					a = doubling[k][a];
					b = doubling[k][b];
				}
			}

			// この時点でa,bはLCAの直下
			lca = doubling[0][a];
		}

		int ans = depth[oa] + depth[ob] - 2 * depth[lca] + 1;
		vecAns.push_back(ans);
	}

	for (int i = 0; i < vecAns.size(); i++) {
		std::cout << vecAns[i] << std::endl;
	}
	return 0;
}

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?