【学习笔记+习题集】树上问题(LCA,树的重心)(不定期更新)(2446字)

板块一:LCA

核心思想:找到第一个公共祖先的子结点

如果两个的深度不同,先调整深度,然后一起向上爬,一直到这个结点。

但是这样时间复杂度太高,所以我们运用倍增的思想,快速逼近这个点。

实现:暂时用oiwiki

#include 
#include 
#include 
#include 
#define MXN 50007
using namespace std;
std::vector v[MXN];
std::vector w[MXN];

int fa[MXN][31], cost[MXN][31], dep[MXN];
int n, m;
int a, b, c;

// dfs,用来为 lca 算法做准备。接受两个参数:dfs 起始节点和它的父亲节点。
void dfs(int root, int fno) {
  // 初始化:第 2^0 = 1 个祖先就是它的父亲节点,dep 也比父亲节点多 1。
  fa[root][0] = fno;
  dep[root] = dep[fa[root][0]] + 1;
  // 初始化:其他的祖先节点:第 2^i 的祖先节点是第 2^(i-1) 的祖先节点的第
  // 2^(i-1) 的祖先节点。
  for (int i = 1; i < 31; ++i) {
    fa[root][i] = fa[fa[root][i - 1]][i - 1];
    cost[root][i] = cost[fa[root][i - 1]][i - 1] + cost[root][i - 1];
  }
  // 遍历子节点来进行 dfs。
  int sz = v[root].size();
  for (int i = 0; i < sz; ++i) {
    if (v[root][i] == fno) continue;
    cost[v[root][i]][0] = w[root][i];
    dfs(v[root][i], root);
  }
}

// lca。用倍增算法算取 x 和 y 的 lca 节点。
int lca(int x, int y) {
  // 令 y 比 x 深。
  if (dep[x] > dep[y]) swap(x, y);
  // 令 y 和 x 在一个深度。
  int tmp = dep[y] - dep[x], ans = 0;
  for (int j = 0; tmp; ++j, tmp >>= 1)
    if (tmp & 1) ans += cost[y][j], y = fa[y][j];
  // 如果这个时候 y = x,那么 x,y 就都是它们自己的祖先。
  if (y == x) return ans;
  // 不然的话,找到第一个不是它们祖先的两个点。
  for (int j = 30; j >= 0 && y != x; --j) {
    if (fa[x][j] != fa[y][j]) {
      ans += cost[x][j] + cost[y][j];
      x = fa[x][j];
      y = fa[y][j];
    }
  }
  // 返回结果。
  ans += cost[x][0] + cost[y][0];
  return ans;
}

int main() {
  // 初始化表示祖先的数组 fa,代价 cost 和深度 dep。
  memset(fa, 0, sizeof(fa));
  memset(cost, 0, sizeof(cost));
  memset(dep, 0, sizeof(dep));
  // 读入树:节点数一共有 n 个。
  scanf("%d", &n);
  for (int i = 1; i < n; ++i) {
    scanf("%d %d %d", &a, &b, &c);
    ++a, ++b;
    v[a].push_back(b);
    v[b].push_back(a);
    w[a].push_back(c);
    w[b].push_back(c);
  }
  // 为了计算 lca 而使用 dfs。
  dfs(1, 0);
  // 查询 m 次,每一次查找两个节点的 lca 点。
  scanf("%d", &m);
  for (int i = 0; i < m; ++i) {
    scanf("%d %d", &a, &b);
    ++a, ++b;
    printf("%d\n", lca(a, b));
  }
  return 0;
}

习题集

版块二:树的重心

性质:

1.以树的重心为根时,所有子树的大小都不超过整棵树大小的一半。

2.树中所有点到某个点的距离和中,到重心的距离和是最小的;如果有两个重心,那么到它们的距离和一样。

3.把两棵树通过一条边相连得到一棵新的树,那么新的树的重心在连接原来两棵树的重心的路径上。

4.在一棵树上添加或删除一个叶子,那么它的重心最多只移动一条边的距离。

证明如此

习题集:

第一题:会议

还需要进一步理解:

#include 
using namespace std;
const int maxn = 100100;
struct node {
	int x, to;
};
node e[maxn];
int idx = 0;
int n, h[maxn], f[maxn], siz[maxn];
void add(int a, int b) {
	e[idx].x = b;
	e[idx].to = h[a];
	h[a] = idx++;
}
void dfs(int x, int fa) {
	siz[x] = 1;
	for (int i = h[x]; i != -1; i = e[i].to) {
		int j = e[i].x;
		if (j == fa) continue;
		dfs(j, x);
		siz[x] += siz[j];
		f[x] = max(f[x], siz[j]);
	}
	f[x] = max(f[x], n - siz[x]);
}
int res = 0;
void dfs1(int x, int fa, int step) {
	res += step;
	for (int i = h[x]; i != -1; i = e[i].to) {
		int j = e[i].x;
		if (j == fa) continue;
		dfs1(j, x, step + 1);
	}
}
int main() {
	memset(h, -1, sizeof(h));
	cin >> n;
	int a, b;
	for (int i = 1; i < n; i++) {
		cin >> a >> b;
		add(a, b);
		add(b, a);
	}
	dfs(1, -1);
	int pos = 1;
	for (int i = 2; i <= n; i++) {
		if (f[i] < f[pos]) pos = i;
	}
	dfs1(pos, -1, 0);
	cout << pos << ' ' << res << '\n';
	return 0;
}

你可能感兴趣的:(学习)