abc 394 题解

ABCD

E

题目大意

给定一张图,对于图上的每两个点问这两点之间存不存在回文路径, 存在的话长度最短是多少

解题思路

(感觉难度 E > F)

首先在空路径的两端不断添加相同的路径,我们可以从短到长地找到所有长度为偶数的回文路径;而在长度为 1 的路径两端不断添加相同的路径,我们可以从短到长地找到所有长度为奇数的回文路径。

CODE
void solve()
{
    int n = 0;
    std::cin >> n;
    std::vector g(n, std::string{});
    for (auto &s : g) {
        std::cin >> s;
    }
    std::vector dis(n, std::vector(n, -1));
    std::queue<std::array<int, 2>> q;
    for (int i = 0; i < n; i++) {
        dis[i][i] = 0;
        // 空白路径 从此拼出长度为偶数的回文路径
        q.push({ i, i });
    }
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            if (i != j && g[i][j] != '-') {
                dis[i][j] = 1;
                // 长度为 1 的路径 从此拼出长度为奇数的回文路径
                q.push({ i, j });
            }
        }
    }

    // 从已有的回文串向两边拓展
    while (not q.empty()) {
        auto [x, y] = q.front();
        q.pop();
        
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (dis[i][j] == -1 && g[i][x] == g[y][j] && g[i][x] != '-') {
                    dis[i][j] = dis[x][y] + 2;
                    q.push({ i, j });
                }
            }
        }
    }
    for (auto &v : dis) {
        for (auto &i : v) {
            std::cout << i << ' ';
        }
        std::cout << '\n';
    }
    return;
}

F

题目大意

定义 alkane 树为满足以下性质的树:

  • 树中每个节点的度都是 1 或 4
  • 至少有一个节点的度是 4

给定一颗树,问这颗树是否存在一个子图构成 alkane 树,存在的话,这个子图中最多有多少个节点。

解题思路

我们定义合法子图是满足以下条件之一的子图:

  • 子图中只有一个节点
  • 子图是由一个节点连接三个合法子图得到的

对以上做出定义后,我们就可以这样得到一颗 alkane 树:

  • 一个节点任意连接四个合法子图得到一颗 alkane 树
  • 一个节点连接任意一个大小不为 1 的合法子图得到一颗 alkane 树

所以我们就可以直接树上 dp 了。具体直接看 dfs 就好了。

CODE
constexpr int N = 2e5, M = 1e5, Inf = 1e9;
std::vector<int> g[N + 5];
// f[i][0] : 以 i 为根节点的 alkane 树的最大大小
// f[i][1] : 以 i 为根节点的合法子图的最大大小
std::vector f(N + 5, std::array<int, 2>{ -1, 1 });

void dfs(int cur, int fa = 0) {
    // 用于维护子节点中前 4 大的合法子图
    std::priority_queue<int> q;
    int mx = 0;
    for (auto &to : g[cur]) {
        if (to == fa) {
            continue;
        }
        
        dfs(to, cur);
        // 取反相当于是大根堆当小根堆用
        q.push(-f[to][1]);
        mx = std::max(mx, f[to][1]);
    }
    // 一个连接大小不为 1 的合法子图得到一颗 alkane 树
    if (mx > 1) {
        f[cur][0] = mx + 1;
    }
    
    // 只保留前 4 大的
    while (q.size() > 4) {
        q.pop();
    }
    int mn = 0;
    if (q.size() == 4) {
        mn = -q.top();
        q.pop();
    }
    if (q.size() == 3) {
        int sum = 0;
        while (not q.empty()) {
            sum -= q.top();
            q.pop();
        }
        // 合法子图的大小
        f[cur][1] = sum + 1;
        
        // 存在第 4 个合法子图时才能构成 alkane 树
        if (mn > 0) {
            f[cur][0] = sum + mn + 1;
        }
    }

}

void solve()
{
    int n = 0;
    std::cin >> n;
    for (int i = 0; i < n - 1; i++) {
        int u = 0, v = 0;
        std::cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }

    dfs(1);
    int ans = -1;
    for (int i = 1; i <= n; i++) {
        ans = std::max(ans, f[i][0]);
    }
    std::cout << ans << '\n';
}

G

题目大意

有一个 \(H \times W\) 的矩阵,格子 \((i, j)\) 中都有一栋最高层数为 \(F_{i, j}\) 的建筑。在同一栋建筑中我们可以上下;在相邻的建筑(共边)之间我们可以平移(即所处层数不变),但是要求平移的终点在不超过目标建筑的顶层。

回答若干询问,每次询问给出起点和终点的坐标和层数,问从起点到终点至少要上下多少层楼。

解题思路

首先从起点是一定可以到达终点的,因为一开始我们就可以降到 1 层,然后可以通过平移到达任何地方,到达终点后再上升就好了。

显然这样不一定是最优的,我们没有必要降到一层,我们只需要降到 \(M\) 层,\(M\) 是从起点到终点所经过的建筑中,层数最低的那栋的高度。于是对于每个询问只要找到了最大的 \(M\),我们就解决了问题。

视相邻的建筑之间有一条边,边权是两栋建筑层数的最小值,于是问题又变成了在两个点间找到一条路径,使这条路径中最小的边权最大。

我的做法是先按 kruskal 最小生成树的算法找到图中的最大生成树。然后对于每个询问,通过树上 st 表找两点的 lca、找生成树上两点间的最小边。(实际上用 kruskal 重构树就只用找 lca,但一开始没想到)。

CODE(生成树)
constexpr int N = 500 * 500, M = 1e6, Inf = 1e9, L = 16;

int fa[N];
int get(int u) {
    return u == fa[u] ? u : (fa[u] = get(fa[u]));
}
bool merge(int u, int v) {
    int fu = get(u), fv = get(v);
    fa[fv] = fu;
    return fu != fv;
}

std::vector<std::array<int, 2>> g[N];
std::vector st(N, std::vector(L, std::array<int, 2>{})); 
std::vector deep(N, 0);
void dfs(int cur) {
    for (int l = 1; (1 << l) <= deep[cur]; l++) {
        st[cur][l][0] = st[st[cur][l - 1][0]][l - 1][0];
        st[cur][l][1] = std::min(st[cur][l - 1][1], st[st[cur][l - 1][0]][l - 1][1]);
    }

    for (auto &[to, val] : g[cur]) {
        if (to == st[cur][0][0]) {
            continue;
        }

        st[to][0] = { cur, val };
        deep[to] = deep[cur] + 1;
        dfs(to);
    }
}

int LCA(int u, int v) {
    if (deep[u] != deep[v]) {
        if (deep[u] > deep[v]) {
            std::swap(u, v);
        }
        for (int i = L; i >= 0; i--) {
            if (deep[v] - (1 << i) >= deep[u]) {
                v = st[v][i][0];
            }
        }
    }

    if (u == v) {
        return u;
    }

    for (int i = L; i >= 0; i--) {
        if ((1 << i) <= deep[u] && st[u][i][0] != st[v][i][0]) {
            u = st[u][i][0];
            v = st[v][i][0];
        }
    }
    return st[u][0][0];
}

int minval(int u, int v) {
    if (deep[u] > deep[v]) {
        std::swap(u, v);
    }
    int res = Inf;
    for (int d = deep[v] -  deep[u], l = 0; d; d >>= 1, l++) {
        if (d & 1) {
            res = std::min(res, st[v][l][1]);
            v = st[v][l][0];
        }
    }
    return res;
}

void solve()
{
    int h = 0, w = 0;
    std::cin >> h >> w;
    std::vector f(h, std::vector(w, 0));
    for (int i = 0; i < h; i++) {
        for (int j = 0; j < w; j++) {
            std::cin >> f[i][j];
        }
    }

    // 分离边
    std::vector<std::array<int, 3>> edge;
    for (int i = 0; i < h; i++) {
        for (int j = 1; j < w; j++) {
            int u = i * w + j;
            edge.push_back({ u - 1, u, std::min( f[i][j], f[i][j - 1]) });
        }
    }
    for (int j = 0; j < w; j++) {
        for (int i = 1; i < h; i++) {
            int u = i * w + j;
            edge.push_back({ u - w, u, std::min( f[i][j], f[i - 1][j]) });
        }
    }

    // 生成树的边
    std::sort(edge.begin(), edge.end(), [](auto &u, auto &v) {
        return u[2] > v[2];
    });
    for (int i = 0; i < h * w; i++) {
        fa[i] = i;
    }
    for (auto &[u, v, val] : edge) {
        if (merge(u, v)) {
            g[u].push_back({ v, val });
            g[v].push_back({ u, val });
        }
    }

    // 生成树上维护 lca 和 st 表
    dfs(0);

    int q = 0;
    std::cin >> q;
    while (q--) {
        int a = 0, b = 0, x = 0;
        int c = 0, d = 0, z = 0;
        std::cin >> a >> b >> x >> c >> d >> z;
        a--, b--, c--, d--;

        int u = a * w + b, v = c * w + d;
        
        int lca = LCA(u, v);
        
        std::cout << x + z - 2 * std::min({ x, z, minval(u, lca), minval(v, lca) }) << '\n';
    }
    return;
}
posted @ 2025-02-27 20:26  Young_Cloud  阅读(37)  评论(0)    收藏  举报