【Coel.学习笔记】基环树动态规划

时间:2022-09-29
本文章向大家介绍【Coel.学习笔记】基环树动态规划,主要内容包括引入、例题讲解、[IOI2008] Island、使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

引入

基环树(又称环套树)是一种特殊的图,在原有的树形结构上添加一条边,就会形成一个环,看起来就像从环延伸出树。特别地,对于有向图而言,环上点所连接的边指向环外为外向树,反之为内向树。

基环树可以看成是只有一个回路的仙人掌,但也因为这个特性,某些基环树的题目具有更灵活的性质,以至于某些基环树题无法用圆方树求解。

例题讲解

[IOI2008] Island

洛谷传送门
给定一个 \(n\) 个点 \(n\) 条边的基环树森林,求每个基环树的最长距离之和。(原题题面太长就不写完了)

解析:类似仙人掌的做法,求解两点最大距离也要进行分类讨论。

  1. 两点在同一棵树上。和仙人掌一样,求出该点向下的最大值和次大值,相加即为答案。
  2. 两个点在不同的树上。不难发现,由于两点属于不同的树,所以从一点到另一点必然要走环,那么就要找环上的较长段。也和仙人掌一样,把距离分成三段:环上最大距离和点到环的最大距离。

当然求解的先决条件是找到环在哪。可以上 tarjan 算法,但是这道题比较特殊,所以只要用一个普通的 dfs 就够了。

#include <iostream>
#include <cstring>

using namespace std;

typedef long long ll;

const int maxn = 2e6 + 10;

int n;
int head[maxn], nxt[maxn], to[maxn], val[maxn], cnt;
int fa[maxn], len[maxn], Q[maxn];
int cir[maxn], ed[maxn], idx;
ll s[maxn], d[maxn], sum[maxn];
bool vis[maxn], ins[maxn];
ll ans, res;

void add(int u, int v, int w) {
	nxt[cnt] = head[u], to[cnt] = v, val[cnt] = w, head[u] = cnt++;
}

void gma(ll &x, ll y) {
	if (x < y) x = y;
}

void dfs(int u, int f) {
	vis[u] = ins[u] = true;
	for (int i = head[u]; ~i; i = nxt[i]) {
		if (i == (f ^ 1)) continue;
		int v = to[i], w = val[i];
		fa[v] = u, len[v] = w;
		if (!vis[v]) dfs(v, i);
		else if (ins[v]) {
			idx++;
			ed[idx] = ed[idx - 1];
			ll sum = val[i];
			for (int k = u; k != v; k = fa[k]) {
				s[k] = sum, sum += len[k];
				cir[++ed[idx]] = k;
			}
			s[v] = sum, cir[++ed[idx]] = v;
		}
	}
	ins[u] = false;
}

ll dfs_d(int u) {
	vis[u] = true;
	ll d1 = 0, d2 = 0;
	for (int i = head[u]; ~i; i = nxt[i]) {
		int v = to[i];
		if (vis[v]) continue;
		ll dis = dfs_d(v) + val[i];
		if (dis >= d1) d2 = d1, d1 = dis;
		else gma(d2, dis);
	}
	gma(res, d1 + d2);
	return d1;
}

int main(void) {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cin >> n;
	memset(head, -1, sizeof(head));
	for (int u = 1; u <= n; u++) {
		int v, w;
		cin >> v >> w;
		add(u, v, w), add(v, u, w);
	}
	for (int i = 1; i <= n; i++)
		if (!vis[i]) dfs(i, -1); //找环
	memset(vis, false, sizeof(vis));
	for (int i = 1; i <= ed[idx]; i++)
		vis[cir[i]] = true; //标记一下每个环,防止搜索时搜到环上的点
	for (int i = 1; i <= idx; i++) {
		res = 0;
		int sz = 0, l = 0, r = -1;
		for (int j = ed[i - 1] + 1; j <= ed[i]; j++) {
			int k = cir[j];
			d[sz] = dfs_d(k);
			sum[sz] = s[k];
			sz++;
		}
		for (int j = 0; j < sz; j++) d[sz + j] = d[j], sum[sz + j] = sum[j] + sum[sz - 1];
		for (int j = 0; j < sz * 2; j++) { //单调队列优化
			if (l <= r && j - Q[l] >= sz) l++;
			if (l <= r) gma(res, d[j] + sum[j] + d[Q[l]] - sum[Q[l]]);
			while (l <= r && d[Q[r]] - sum[Q[r]] <= d[j] - sum[j]) r--;
			Q[++r] = j;
		}
		ans += res;
	}
	cout << ans;
	return 0;
}

原文地址:https://www.cnblogs.com/Coel-Flannette/p/16743074.html