• 考虑断开环上的边,转化为树上问题,最后考虑该边的影响
  • 去掉环,此时每个点均构成一棵大小大于等于 $1$ 的子树,然后对环进行处理。

旅行,Luogu P5022

断边处理。

#include <bits/stdc++.h>

using namespace std;

inline int read() {
    int x = 0, f = 1; char ch = getchar();
    for (; !isdigit(ch); ch = getchar()) f -= (ch == '-') * 2;
    for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48);
    return x * f;
}

const int N = 5010, M = 10010;

int n, m;
int dfn[N], fa[N], cnt;
int del[N][N];
vector<vector<int>> rings;
vector<int> ring;
vector<int> ans;
vector<int> g[N];

void add(int a, int b) {
    g[a].push_back(b);
}

void getring(int u) {
    dfn[u] = ++ cnt;
    for (int i = 0; i < g[u].size(); i ++ ) {
        int j = g[u][i];
        if (j == fa[u]) continue;
        if (!dfn[j]) {
            fa[j] = u;
            getring(j);
        }
        else if (dfn[u] < dfn[j]) {
            ring.clear();
            ring.push_back(j);
            do { ring.push_back(fa[j]), j = fa[j]; }
            while (j != u);
            rings.push_back(ring);
        }
    }
}

void dfs(int u, int fa) {
    ans.push_back(u);
    for (int i = 0; i < g[u].size(); i ++ ) {
        int j = g[u][i];
        if (j == fa) continue;
        if (del[u][j] || del[j][u]) continue;
        dfs(j, u);
    }
}

int main() {
    n = read(), m = read();
    for (int i = 1; i <= m; i ++ ) {
        int a = read(), b = read();
        add(a, b), add(b, a);
    }
    for (int i = 1; i <= n; i ++ ) sort(g[i].begin(), g[i].end());
    getring(1);
    vector<int> res(n, 1e9);
    if (rings.size() == 0) {
        dfs(1, 0);
        res = ans;
    }
    else {
        for (int i = 0; i < ring.size() - 1; i ++ ) {
            del[ring[i]][ring[i + 1]] = true;
            del[ring[i + 1]][ring[i]] = true;
            ans.clear();
            dfs(1, 0);
            if (ans < res) res = ans;
            del[ring[i]][ring[i + 1]] = false;
            del[ring[i + 1]][ring[i]] = false;
        }
    }
    for (int i = 0; i < res.size(); i ++ ) printf("%d ", res[i]);
    return 0;
}

Island 岛屿(AcWing & Luogu P4381)

基环树直径。

考虑去掉树上的环,答案有两种情况:

  • 直径在某棵子树上
  • 直径经过环上的边

对于第一种情况,答案为最大的子树的直径。

对于第二种情况,设 $a, b$ 分别为环上边的两端,则答案可以分割为三部分:$a, b$ 的长度,以 $a$ 为根从 $a$ 出发到达的最远距离(不经过环上的点),以 $b$ 为根从 $b$ 出发到达的最远距离(不经过环上的点)。

即 $dist_{a, b} + f_a + f_b$,时间复杂度 $O(n^2)$,不足以通过此题,将链上的距离做前缀和,原式变为 $sum_b - sum_a + f_a + f_b$。

可以使用单调队列优化。

关于双指针:待填坑
#include <bits/stdc++.h>

using namespace std;

#define int long long

inline int read() {
    int x = 0, f = 1; char ch = getchar();
    for (; !isdigit(ch); ch = getchar()) f -= (ch == '-') * 2;
    for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48);
    return x * f;
}

const int N = 1000010, M = 2 * N;

int n, cnt, rt, res = 0, ans = 0;
int h[N], e[M], ne[M], w[M], idx;
int dfn[N], d[N], s[N], fa[N], q[N];
vector<int> ring;
bool vis[N];

void add(int a, int b, int c) {
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

void add_ring(int a, int b, int c) {
    ring.clear();
    s[1] = c;
    ring.push_back(b);
    s[2] = w[fa[b]];

    do {
        int u = e[fa[b] ^ 1];
        ring.push_back(u);
        s[ring.size() + 1] = w[fa[u]];
        b = e[fa[b] ^ 1];
    } while (a != b);

    int sz = ring.size();
    for (int i = 1; i <= sz; i ++ ) {
        vis[ring[i - 1]] = true;
        ring.push_back(ring[i - 1]);
        s[sz + i] = s[i];
    }

    for (int i = 1; i <= ring.size(); i ++ ) s[i] += s[i - 1];
}

void getring(int u, int f) {
    dfn[u] = ++ cnt;
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (j == f) continue;
        if (!dfn[j]) {
            fa[j] = i;
            getring(j, u);
        }
        else if (dfn[u] < dfn[j]) add_ring(u, j, w[i]);
    }
}

void dp(int u) {
    vis[u] = true;
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (vis[j]) continue;
        dp(j);
        res = max(res, d[u] + d[j] + w[i]);
        d[u] = max(d[u], d[j] + w[i]);
    }
}

int on_the_ring() {
    int hh = 0, tt = -1;
    int sz = ring.size() / 2;
    int tmp = 0;
    for (int i = 1; i <= 2 * sz; i ++ ) {
        while (hh <= tt && q[hh] < i - sz + 1) hh ++ ;
        if (hh <= tt) tmp = max(tmp, d[ring[i - 1]] + d[ring[q[hh] - 1]] + s[i] - s[q[hh]]);
        while (hh <= tt && d[ring[q[tt] - 1]] - s[q[tt]] <= d[ring[i - 1]] - s[i]) tt -- ;
        q[ ++ tt] = i;
    }
    return tmp;
}

signed main() {
    memset(h, -1, sizeof h);
    n = read();
    for (int i = 1; i <= n; i ++ ) {
        int b = read(), c = read();
        add(i, b, c), add(b, i, c);
    }
    for (int i = 1; i <= n; i ++ ) {
        if (dfn[i]) continue;
        getring(i, -1);
        res = 0;
        for (int j = 0; j < ring.size(); j ++ ) dp(ring[j]);
        res = max(res, on_the_ring());
        ans += res;
    }
    printf("%lld\n", ans);
    return 0;
}
最后修改:2023 年 07 月 06 日
赠人玫瑰,手有余香。您的赞赏是对我最大的支持!