1 条题解

  • 0
    @ 2025-10-22 17:45:34

    「School Road」通学路题解

    问题分析

    我们需要判断是否存在一条从城市 NN 到城市 11 的路径,满足:

    1. 长度大于最短距离 LL
    2. 不重复经过任何城市(简单路径)

    核心思路

    这个问题可以通过图论和缩点技术来解决。关键观察是:如果存在多条不同的最短路径或者存在可以"绕路"的结构,那么就可能存在满足条件的路径。

    算法详解

    1. 计算最短路径

    for (int i = 1; i <= n; i++)
        dis[i] = inf;
    
    priority_queue<pii> q;
    dis[1] = 0;
    q.push({0, 1});
    
    while (!q.empty()) {
        int u = q.top().se;
        q.pop();
        if (vis[u]) continue;
        vis[u] = 1;
        for (int i = head[u]; i; i = e[i].nxt) {
            int v = e[i].to;
            if (dis[v] > dis[u] + e[i].w) {
                dis[v] = dis[u] + e[i].w;
                q.push({-dis[v], v});
            }
        }
    }
    

    使用Dijkstra算法计算从城市 11 到所有城市的最短距离。

    2. 构建最短路径图并找点双连通分量

    add_e(1, n, dis[n]), add_e(n, 1, dis[n]);
    tar(1);
    

    添加一条从 11NN 的虚拟边(长度为最短距离),然后使用Tarjan算法找点双连通分量。

    void tar(int u) {
        dfn[u] = lw[u] = ++idx;
        st[++tp] = u;
        for (int i = head[u]; i; i = e[i].nxt) {
            int v = e[i].to;
            if (!dfn[v]) {
                tar(v);
                lw[u] = min(lw[u], lw[v]);
                if (lw[v] >= dfn[u]) {
                    id[++scct].push_back(st[tp]);
                    while (st[tp--] != v) {
                        id[scct].push_back(st[tp]);
                    }
                    id[scct].push_back(u);
                }
            } else
                lw[u] = min(lw[u], dfn[v]);
        }
    }
    

    3. 识别关键的双连通分量

    for (int i = 1; i <= scct; i++) {
        int tim = 0;
        for (int j : id[i])
            tim += (j == 1) + (j == n);
        if (tim == 2) {
            for (int j : id[i])
                bk[j] = 1;
            break;
        }
    }
    

    找到包含起点 11 和终点 NN 的点双连通分量,这里面的节点构成了可能绕路的关键区域。

    4. 构建简化图并进行缩点

    for (int u = 1; u <= n; u++) {
        for (int i = head[u]; i; i = e[i].nxt) {
            int v = e[i].to;
            if (bk[u] && bk[v] && u < v)
                add(u, v, e[i].w);
        }
    }
    

    在关键区域内构建简化图,然后进行度数-based的缩点:

    queue<int> qq;
    for (int i = 2; i < n; i++)
        if (d[i] <= 2)
            qq.push(i);
    
    while (!qq.empty()) {
        int u = qq.front();
        qq.pop();
        if (!d[u]) continue;
        else if (d[u] == 1) {
            // 删除度数为1的节点(非起点终点)
            int v = (*f[u].begin()).fi;
            d[u]--, d[v]--;
            f[u].erase(v), f[v].erase(u);
            if (v != 1 && v != n && d[v] <= 2)
                qq.push(v);
        } else {
            // 合并度数为2的节点
            int v1 = (*f[u].begin()).fi, v2 = (*--f[u].end()).fi;
            add(v1, v2, (f[u][v1] == -1 || f[u][v2] == -1) ? -1 : f[u][v1] + f[u][v2]);
            d[u] -= 2, d[v1]--, d[v2]--;
            f[u].erase(v1), f[v1].erase(u), f[u].erase(v2), f[v2].erase(u);
            if (v1 != 1 && v1 != n && d[v1] <= 2) qq.push(v1);
            if (v2 != 1 && v2 != n && d[v2] <= 2) qq.push(v2);
        }
    }
    

    5. 判断结果

    if (f[1].size() > 1 || f[1][n] == -1)
        puts("1");
    else
        puts("0");
    

    如果:

    • 起点 11 有多个邻居(意味着存在选择)
    • 或者 11NN 的边权为 1-1(意味着存在多条不同长度的路径)

    则输出 11,否则输出 00

    算法正确性

    为什么这样能判断是否存在绕路?

    1. 点双连通分量:包含 11NN 的点双意味着存在多条不相交的路径
    2. 度数缩点:将复杂网络简化为只保留关键结构
    3. 边权为-1:表示存在多条不同长度的路径,这正好对应绕路的可能性

    时间复杂度

    • DijkstraO((n+m)logn)O((n+m)\log n)
    • TarjanO(n+m)O(n+m)
    • 缩点O(n)O(n)
    • 总复杂度O((n+m)logn)O((n+m)\log n),满足数据范围要求

    样例验证

    样例1:输出0

    • 所有路径长度都等于最短距离 55
    • 没有绕路可能

    样例2:输出1

    • 存在长度为 66 的路径 4314\to 3\to 1
    • 满足绕路条件

    样例4:输出1

    • 额外的边 232-3 创造了绕路机会

    总结

    本题的巧妙之处在于:

    1. 问题转化:将绕路问题转化为图的结构性质问题
    2. 点双连通分量:识别包含起点终点的关键区域
    3. 度数-based缩点:简化图结构同时保留关键信息
    4. 边权标记:用 1-1 表示存在长度选择

    这种"图结构分析 + 缩点简化"的方法在解决路径相关问题时非常有效,特别是当需要判断存在性而非具体构造时。

    • 1

    信息

    ID
    3742
    时间
    1000ms
    内存
    256MiB
    难度
    8
    标签
    递交数
    4
    已通过
    1
    上传者