Skip to content

F. Variables and Operations - 题解

比赛与标签

比赛: Educational Codeforces Round 180 (Rated for Div. 2) 标签: graphs, greedy, shortest paths 难度: *2800

题目大意喵~

主人,你好呀!这道题是这样的喵~

我们有 n 个变量 a_1, a_2, ..., a_n,还有 m 个操作。每个操作都形如 (x, y, z),它的效果是把变量 a_x 的值更新为 min(a_x, a_y + z)

这些操作每一个都会被执行一次,但是执行的顺序是任意的。如果对于一套初始的变量值,无论按什么顺序执行这 m 个操作,最后所有变量的最终值都完全相同,我们就说这套初始值是 稳定 (stable) 的。反之,如果存在某个变量 a_i 的最终值会因为操作顺序的不同而改变,我们就说这套初始值是 i-不稳定 (i-unstable) 的。

接下来有 q 次询问。每次询问会给你一套初始值 a_1, ..., a_n 和一个整数 k。在执行操作之前,我们最多可以进行 k 次“选择一个变量并将其值减 1”的操作。对于每个变量 i(从 1 到 n),你需要独立判断,我们是否能通过这至多 k 次的减一操作,使得初始值序列变为 i-不稳定?

如果可以,就输出 '1';如果不可以,就输出 '0'。每个询问要输出一个长度为 n 的 01 字符串哦~

解题思路,启动!

这道题看起来有点复杂,又是操作顺序又是稳定性的,但别怕,跟着本猫娘的思路一步步来,很快就能搞明白的啦,喵~

第一步:把问题变成图论模型!

看到 a_x = min(a_x, a_y + z) 这种形式,聪明的你是不是立刻就想到了什么?对啦!这不就是图论里最短路算法的 松弛操作 (relaxation) 嘛!

我们可以把 n 个变量看作是图上的 n 个节点。每一个操作 (x, y, z) 都可以看作是一条从节点 y 到节点 x 的有向边,边的权重就是 z

这样一来,对变量 a_x 的更新,就相当于在图上用从 yx 的边来尝试松弛 x 的距离。

第二步:不稳定的根源在哪里喵?

一个序列是稳定的,意味着无论我们怎么折腾操作顺序,最终结果都一样。那么,不稳定又是怎么产生的呢?

想象一下,我们要更新 a_i 的值,有一个操作是 (i, j, z)

  • 情况一:我们很早就执行这个操作。此时 a_j 可能还是它的初始值。a_i 会被更新为 min(a_i, a_j_{initial} + z)
  • 情况二:我们先不执行 (i, j, z),而是先执行了其他一些操作,这些操作恰好更新了 a_j 的值,让它变小了。然后再执行 (i, j, z)。这时 a_i 就会被更新为 min(a_i, a_j_{new} + z)

如果 a_j_{new} < a_j_{initial},那么情况二得到的 a_i 就有可能比情况一更小!这就导致了 a_i 的最终值与顺序有关,也就是 i-不稳定

这种“先更新 j 再更新 i”的过程,在图上对应着什么呢?它对应着一条从某个节点 p 出发,经过一系列节点到达 j,最后再通过 (i, j, z) 这条边到达 i 的路径!也就是 p -> ... -> j -> i

所以,不稳定的核心在于:存在一条直连边 j -> i,但同时又存在另一条从 ji 的、由多条边组成的、并且总权值更短的路径!

adj[j][i] 为操作 (i, j, z) 对应的直连边权重 z。设 dist[j][i] 为从 ji 的最短路径长度。 i-不稳定的可能性就出现在存在某个 j,使得 dist[j][i] < adj[j][i]

我们可以用 Floyd-Warshall 算法,在 O(n^3) 的时间内预处理出所有点对之间的最短路 dist

第三步:花多少代价才能变得不稳定?

好啦,我们找到了不稳定的条件:存在 j 使得 dist[j][i] < adj[j][i]。现在的问题是,需要花多少代价(减一操作)才能让这种不稳定性真正“触发”。

为了触发不稳定性,我们需要让通过 j 的那条“捷径” (j -> ... -> i) 算出的值,比其他所有能更新 a_i 的方式都要小。

我们来定义一个基准值 mn[i],它代表在不考虑多步路径的情况下,a_i 能达到的最优值。也就是只考虑所有 (i, p, w) 操作一步更新能达到的最小值: mn[i] = min(a_i, min_{所有p} (a_p + adj[p][i])) 这里的 a 是我们修改前(或修改后)的数组。

现在,我们想让 j 的那条捷径胜出。通过捷径传到 i 的值是 a_j + dist[j][i]。我们希望这个值能比 mn[i] 还要小,比如说,我们想让它变成 mn[i] - 1a'_j + dist[j][i] = mn[i] - 1

为了达到这个目标,最划算的方法当然是只减小 a_j 的值啦。设我们把 a_j 减小了 cost,变成了 a'_j = a_j - cost。 代入上式:(a_j - cost) + dist[j][i] = mn[i] - 1 解得:cost = a_j + dist[j][i] - (mn[i] - 1)

这就是为了让 j 这条捷径生效,我们需要付出的最小代价!

第四步:整合算法

现在,对于每个询问,我们的完整策略就很清晰了喵:

  1. 首先,在所有询问开始前,用 Floyd-Warshall 算法预计算出所有点对间的最短路 dist
  2. 对于每个询问,给定 k 和初始数组 a
  3. 计算出基准值数组 mn,其中 mn[i] = min(a_i, min_{p=1..n}(a_p + adj[p][i]))
  4. 对于每个目标变量 i (从 1 到 n): a. 我们想知道让它变得不稳定的最小代价。这个代价是所有可能的“捷径” j 中最小的那个。 b. 初始化一个 min_cost = infinity。 c. 遍历所有可能的中间节点 j (从 1 到 n)。 d. 如果 dist[j][i] < adj[j][i](即 j 是一个潜在的捷径),计算通过它实现不稳定性的代价:cost_j = a_j + dist[j][i] - mn[i] + 1。 e. 更新 min_cost = min(min_cost, cost_j)。 f. 循环结束后,如果 min_cost <= k,说明我们有足够的预算让 a_i 变得不稳定,输出 '1'。否则输出 '0'。

这样,我们就完美解决问题啦!是不是感觉思路清晰多啦?喵~

代码实现,请看这里喵!

cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>

using namespace std;
typedef long long ll;
const ll INF = 1e18; // 用一个足够大的数表示无穷大,防止溢出喵

int main() {
    // 加速输入输出,让程序跑得更快一点~
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, m;
    cin >> n >> m;

    // adj[y][x] 存的是从 y 到 x 的直接操作的最小代价 z
    vector<vector<ll>> adj(n + 1, vector<ll>(n + 1, INF));
    for (int i = 1; i <= n; ++i) {
        adj[i][i] = 0; // 自己到自己的距离是0
    }

    // 读入 m 个操作,构建邻接矩阵
    for (int i = 0; i < m; ++i) {
        int x, y, z;
        cin >> x >> y >> z;
        // 如果有多个从 y 到 x 的操作,我们只关心那个 z最小的
        if (z < adj[y][x]) {
            adj[y][x] = z;
        }
    }

    // 使用 Floyd-Warshall 算法计算所有点对之间的最短路
    // dist[i][j] 表示从 i 到 j 的最短路径长度
    vector<vector<ll>> dist = adj;
    for (int k = 1; k <= n; ++k) {
        for (int i = 1; i <= n; ++i) {
            if (dist[i][k] == INF) continue; // 优化:如果 i->k 不通,就跳过
            for (int j = 1; j <= n; ++j) {
                if (dist[k][j] != INF) { // 优化:如果 k->j 不通,也跳过
                    // 松弛操作:看看经过 k 能不能让 i->j 的路径更短
                    if (dist[i][j] > dist[i][k] + dist[k][j]) {
                        dist[i][j] = dist[i][k] + dist[k][j];
                    }
                }
            }
        }
    }

    int q;
    cin >> q;
    while (q--) {
        ll k;
        cin >> k;
        vector<ll> a(n + 1);
        for (int i = 1; i <= n; ++i) {
            cin >> a[i];
        }

        // 计算基准值 mn[i],即只考虑一步操作能达到的最小值
        vector<ll> mn(n + 1, INF);
        for (int i = 1; i <= n; ++i) {
            mn[i] = a[i];
        }
        for (int i = 1; i <= n; ++i) { // 源点 i
            for (int j = 1; j <= n; ++j) { // 目标点 j
                if (adj[i][j] != INF) {
                    // 用 a[i] + adj[i][j] 更新 mn[j]
                    if (a[i] + adj[i][j] < mn[j]) {
                        mn[j] = a[i] + adj[i][j];
                    }
                }
            }
        }

        string res = "";
        // 对每个变量 i,独立判断是否能使其不稳定
        for (int i = 1; i <= n; ++i) {
            ll need = INF; // 找到让 i 不稳定的最小花费
            // 遍历所有可能的“捷径”来源 j
            for (int j = 1; j <= n; ++j) {
                // 这是不稳定的核心条件:存在一条比直连边更短的路径
                if (dist[j][i] < adj[j][i]) {
                    // 计算需要花费的代价,使得捷径的值 a[j]+dist[j][i] 小于基准值 mn[i]
                    ll candidate = a[j] + dist[j][i] - (mn[i] - 1);
                    if (candidate < need) {
                        need = candidate;
                    }
                }
            }

            // 如果找到的最小花费在我们的预算 k 之内
            if (need <= k) {
                res += '1'; // 可以做到!
            } else {
                res += '0'; // 预算不够QAQ
            }
        }
        cout << res << '\n';
    }

    return 0;
}

复杂度分析

  • 时间复杂度: O(n³ + q * n²) 的说。

    • 来自于预处理所有点对最短路径的 Floyd-Warshall 算法。
    • 对于 q 次询问,每次询问都需要 O(n²) 的时间来计算 mn 数组,以及 O(n²) 的时间来为每个 i 找到最小的 need。所以每次询问是 O(n²)
    • 总和就是 O(n³ + q * n²)。考虑到 n 最大 500,q 最大 1000,这个复杂度是可以通过的~
  • 空间复杂度: O(n²) 的说。

    • 我们需要存储 adj 邻接矩阵和 dist 最短路矩阵,都需要 O(n²) 的空间。其他变量都是 O(n) 的,所以空间瓶颈在这里。

知识点与总结

这道题是一道非常巧妙的图论题,把一个看似和图无关的问题转化为了最短路模型,真是太棒了喵!

  1. 图论建模: 学会将问题中的实体和关系抽象成图的节点和边,是解决很多算法难题的第一步。这里的 a_x = min(a_x, a_y + z) 就是一个强烈的信号!
  2. All-Pairs Shortest Path: Floyd-Warshall 算法是解决所有点对最短路问题的经典方法,尤其适用于点数不多(几百个)的稠密图。
  3. 不稳定的本质: 理解不稳定性来源于“不同路径的竞争”。当存在一条“捷径”(dist[j][i] < adj[j][i])时,就为操作顺序制造不同结果提供了可能。
  4. 贪心与代价计算: 在计算最小花费时,我们贪心地选择只降低最关键的变量 a_j,并设定一个明确的目标(比基准值小 1),从而推导出代价公式。这是一种化繁为简的有效策略。

希望本猫娘的讲解能帮助到你!继续加油,算法的世界还有更多有趣的冒险在等着我们呢,喵~!

Released under the MIT License.