引入
基环树(又称环套树)是一种特殊的图,在原有的树形结构上添加一条边,就会形成一个环,看起来就像从环延伸出树。特别地,对于有向图而言,环上点所连接的边指向环外为外向树,反之为内向树。
基环树可以看成是只有一个回路的仙人掌,但也因为这个特性,某些基环树的题目具有更灵活的性质,以至于某些基环树题无法用圆方树求解。
例题讲解
[IOI2008] Island
洛谷传送门
给定一个 \(n\) 个点 \(n\) 条边的基环树森林,求每个基环树的最长距离之和。(原题题面太长就不写完了)
解析:类似仙人掌的做法,求解两点最大距离也要进行分类讨论。
- 两点在同一棵树上。和仙人掌一样,求出该点向下的最大值和次大值,相加即为答案。
- 两个点在不同的树上。不难发现,由于两点属于不同的树,所以从一点到另一点必然要走环,那么就要找环上的较长段。也和仙人掌一样,把距离分成三段:环上最大距离和点到环的最大距离。
当然求解的先决条件是找到环在哪。可以上 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;
}
标签:head,int,ll,Coel,笔记,基环树,maxn,d1
From: https://www.cnblogs.com/Coel-Flannette/p/16743074.html