洛谷 P4366 题解

P4366 [Code+#4] 最短路

题意简述

有一张 \(N\) 个点的完全图,第 \(i\) 个点和第 \(j\) 个点之间边的边权为 \((i \oplus j) \times C\),其中 \(C\) 是一个给定的常数。此外给定 \(M\) 条有向边。给定两点 \(A, B\),求两点间的最短路。

分析

引入

\(N \le 10 ^ 5\),显然无法将完全图中的每条边全部存下。我们希望找到一些多余的边。具体地,在最短路模型中,这些边可以被其他的路径替代。

例如,第 \(2\) 个点与第 \(7\) 个点之间有一条边权为 \((2 \oplus 7) \times C = 5 \times C\) 的边。同时我们发现,从第 \(2\) 个点到第 \(3\) 个点,再从第 \(3\) 个点到第 \(7\) 个点,所需代价为 \((2 \oplus 3) \times C + (3 \oplus 7) \times C = 5 \times C\)。因此第 \(2\) 个点与第 \(7\) 个点之间这条边是多余的,它可以被 \(2 \to 3 \to 7\) 这条路径替代。

结论

一般地,我们希望找出所有多余的边。具体地,对于 \(i, j\),如果存在 \(t\) 满足 \(t \ne i, t \ne j\)\(i \oplus j = (i \oplus t) + (t \oplus j)\),那么第 \(i\) 个点和第 \(j\) 个点之间的边就是多余的。

结论是:仅对形如 \(i = (a _ s a _ {s - 1} \cdots a _ {k + 1} 0 a _ {k - 1} \cdots a _ 1 a _ 0) _ 2, j = (a _ s a _ {s - 1} \cdots a _ {k + 1} 1 a _ {k - 1} \cdots a _ 1 a _ 0) _ 2\)\(i, j\),在第 \(i\) 个点和第 \(j\) 个点之间连双向边。
简单来说,如果 \(i, j\) 满足 \(j = i + 2 ^ k\),并且 \(i + 2 ^ k\) 在二进制下没有导致进位,那么在第 \(i\) 个点和第 \(j\) 个点之间连双向边。下面给出证明。

证明

必要性

首先我们证明这些边是必需的,它们无法被其他边替代。

对于满足条件的 \(i, j\),假设存在 \(t\) 满足 \(t \ne i, t \ne j\)\(i \oplus j = (i \oplus t) + (t \oplus j)\)。设 \(t = (b _ s b _ {s - 1} \cdots b _ {k + 1} b _ k b _ {k - 1} \cdots b _ 1 b _ 0) _ 2\)

考察 \(t\) 的第 \(k\)\(b _ k\)。显然 \(i \oplus j = 2 ^ k\),即除了第 \(k\) 位为 \(1\) 其他位都为 \(0\)。注意到 \((i \oplus t) + (t \oplus j)\) 的第 \(k\)\((b _ k \oplus 0) + (b _ k \oplus 1) \equiv 1\),因此 \(b _ k\)\(0, 1\) 都满足条件,并且第 \(k\) 位的 \(1\) 不可能由第 \(k - 1\) 位进位得到。

考察 \(t\) 的其他位。\((i \oplus t) + (t \oplus j)\) 的第 \(0\)\((a_0 \oplus b_0) + (b _ 0 \oplus a _ 0) = 2 \times (a_0 \oplus b_0)\) 只可能为 \(0\)\(2\)。假设为 \(2\),那么就会导致进位。类似地,下一位 \((a _ 1 \oplus b _ 1) + (b _ 1 \oplus a _ 1)\) 只可能为 \(0\)\(2\)。无论是哪一种加上第 \(0\) 位进的 \(1\) 都会得到 \(1\)。由刚才的讨论 \(i \oplus j\) 的第 \(k\)\(1\) 不可能由第 \(k - 1\) 位进位得到,所以下一位不是第 \(k\) 位,故 \(i \oplus j\) 的下一位为 \(0\)。这与 \((a _ 1 \oplus b _ 1) + (b _ 1 \oplus a _ 1)\) 加上进位 \(1\)\(1\) 矛盾。因此 \((i \oplus t) + (t \oplus j)\) 的第 \(0\) 位只能为 \(0\),此时有 \(b _ 0 = a _ 0\)。同理可证对于任意 \(x \ne k\) 都有 \(b _ x = a _ x\),即 \(t\) 除了第 \(k\) 位都与 \(i, j\)​ 相同。

综上,\(t\) 的第 \(k\) 位取 \(0, 1\) 都满足条件,而 \(t\) 除了第 \(k\) 位都与 \(i, j\) 相同,那么就有 \(t = i\)\(t = j\),与假设矛盾。所以不存在这样的 \(t\)

充分性

首先我们证明,对于形如 \(i = (a _ s a _ {s - 1} \cdots a _ r 0 1 1 \cdots 1 1 1 a _ l \cdots a _ 1 a _ 0) _ 2, j = (a _ s a _ {s - 1} \cdots a _ r 100 \cdots 000 a _ l \cdots a _ 1 a _ 0) _ 2\),即 \(i, j\) 满足 \(j = i + 2 ^ k\),并且 \(i + 2 ^ k\) 导致了进位的 \(i, j\),由我们的连边方式可以找到一条路径替代第 \(i\) 个点和第 \(j\) 个点之间的边。

\(t\) 的构造方法是 \(t = (a _ s a _ {s - 1} \cdots a _ r 000 \cdots 000 a _ l \cdots a _ 1 a _ 0) _ 2\),即 \(t\)\(i, j\) 不同的位上都取 \(0\),其他位与 \(i, j\) 相同。显然可以验证

\[i \oplus j = (i \oplus t) + (t \oplus j) = (\underbrace{111 \cdots 111} _ {r - l - 1 \text{ 个 } 1} \overbrace{000 \cdots 000} ^ {l + 1 \text{ 个 } 0}) _ 2 \]

存在特殊情况 \(i = (011 \cdots 111) _ 2, j = (100 \cdots 000) _ 2\),此时 \(t = 0\)。为此我们人为地引入第 \(0\) 个点。对于第 \(0\) 个点使用相同的方法连边,这样就解决了特殊情况。

综上,对于满足 \(j = i + 2 ^ k\)\(i, j\),若 \(i + 2 ^ k\) 不进位则 \(i, j\) 之间有一条边,若 \(i + 2 ^ k\) 进位则可以找到等价的一条路径。所以我们解决了 \(i, j\) 满足 \(j = i + 2 ^ k\) 的情况。显然,对于任意 \(i, j \ (i \le j)\)\(i\) 加上若干个 \(2 ^ k\) 总能得到 \(j\),因此等价的路径总是存在,证毕。

复杂度分析

边的数量为 \(m = N \log N + M\),使用堆优化的 Dijkstra 求最短路的时间复杂度为 \(O(m \log m)\)。极限情况下 \(m\)\(2 \times 10 ^ 6\)​ 左右,较为勉强但可以通过。

实现

提供一种优雅的连边方法:

for (int i = 0; i <= n; i++)
    for (int j = 1; j <= n; j <<= 1)
        if ((i ^ j) <= n)
            edge[i].push_back({i ^ j, c * j});

j 即为 \(2 ^ k\)i ^ j 会把 i 的第 \(k\) 位自动取反。具体地,若 i 的第 \(k\) 位为 \(0\),则会连接 ii + j;若 i 的第 \(k\) 位为 \(1\),则会连接 ii - j。这样跑完之后就会连完所有的双向边。

完整代码:

#include <cstring>
#include <iostream>
#include <bitset>
#include <vector>
#include <queue>

struct Edge
{
    int v, w;
    friend bool operator>(Edge _a, Edge _b) { return _a.w > _b.w; }
};

int n, c, a, b;
int dis[100005];
std::bitset<100005> vis;
std::vector<Edge> edge[100005];
std::priority_queue<Edge, std::vector<Edge>, std::greater<Edge>> pq;

void dijkstra()
{
    memset(dis, 0x3f, sizeof(int) * (n + 1)); // 卡常小技巧
    dis[a] = 0;
    pq.push({a, 0});

    while (!pq.empty())
    {
        int u = pq.top().v;
        pq.pop();
        if (vis[u])
            continue;
        if (u == b) // 卡常小技巧
            return;
        vis[u] = true;
        for (auto x : edge[u])
        {
            int v = x.v, w = x.w;
            if (dis[v] > dis[u] + w)
            {
                dis[v] = dis[u] + w;
                pq.push({v, dis[v]});
            }
        }
    }
}

int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);

    int m;

    std::cin >> n >> m >> c;
    for (int i = 1; i <= m; i++) // 特殊的 M 条边
    {
        int f, t, v;
        std::cin >> f >> t >> v;
        edge[f].push_back({t, v});
    }
    std::cin >> a >> b;

    for (int i = 0; i <= n; i++) // 建边
        for (int j = 1; j <= n; j <<= 1)
            if ((i ^ j) <= n)
                edge[i].push_back({i ^ j, c * j});

    dijkstra();

    std::cout << dis[b] << "\n";

    return 0;
}
posted @ 2024-04-28 23:57  lzy20091001  阅读(80)  评论(0)    收藏  举报