Official

E - 信号変換器の出力種類数 / Number of Distinct Outputs of a Signal Converter Editorial by admin

gemini-3-flash-thinking

Overview

The problem asks us to efficiently determine what value \(F(s)\) each initial value \(s \in [1, K]\) is transformed into after \(N\) conversion steps, and then answer queries of the form “how many distinct values of \(F(s)\) are contained in the range \([A_j, B_j]\).”

Analysis

1. Efficient Simulation of the Transformation Process

Naively updating all signal values at each step would result in a worst-case complexity of \(O(NK)\), which is too slow under the constraints (\(N, K \leq 2 \times 10^5\)). Here, we focus on the key property: “signals that have the same value will always behave identically in all subsequent transformations.”

Initially, \(1, 2, \dots, K\) are all distinct values, but at a certain step, all values in the range \([L_i, R_i]\) are rewritten to \(X_i\) and merged into a single group. DSU (Union-Find) is well-suited for managing this “group merging.”

2. Tracking Signal Values

We manage the “distinct values” currently present in the converter using a data structure such as std::set. At each step \((L_i, R_i, X_i)\), we perform the following operations: 1. Find all values in the range \([L_i, R_i]\) from the set. 2. Remove those values from the set and merge them into a single group using DSU. 3. Set the current value pointed to by the representative of the merged group to \(X_i\), and add \(X_i\) to the set.

With this approach, once values are merged they never split apart, so the total number of insertions and deletions to the set is bounded by \(O(N+K)\).

3. Distinct Count Queries Over a Range

After all steps are complete, the final output \(F(s)\) for each \(s\) is determined. Next, we need to find the number of distinct values in specific intervals \([A_j, B_j]\) of the array \(F\). This is a classic competitive programming problem that can be solved using offline queries and a Fenwick Tree (BIT).

  • Sort queries in ascending order of right endpoint \(B_j\).
  • Scan the array from left to right, recording the “last occurrence position” of each value.
  • When value \(v\) appears at a new position \(i\):
    • If \(v\) has appeared at a previous position, subtract \(1\) from that position in the BIT.
    • Add \(1\) at the current position \(i\) in the BIT.
  • In this state, the sum over the interval \([A_j, B_j]\) in the BIT gives the number of distinct values.

Algorithm

  1. Simulation with DSU and Set

    • pos[v]: the group (DSU root) of initial values that currently hold value \(v\).
    • current_value[root]: the signal value currently held by DSU root root.
    • S: the set of signal values currently present in the converter.
    • At each step, use S.lower_bound(L_i) to find and batch-merge/update values within the range.
  2. Offline Distinct Count

    • Store \(F(s)\) in an array after all steps are complete.
    • Sort queries by \(B_j\) and compute distinct counts by scanning with a BIT.

Complexity

  • Time Complexity: \(O((N + K + Q) \log K)\)
    • Simulation part: Each element is added to and removed from the set at most a constant number of times, giving \(O((N+K) \log K)\).
    • Distinct count query part: Sorting and BIT operations give \(O((K+Q) \log K)\).
  • Space Complexity: \(O(N + K + Q)\)
    • Memory required for DSU, BIT, array \(F\), and storing queries.

Implementation Notes

  • Set iterator management: When deleting elements within a range from the set, you must first collect the elements to be deleted into a list before actually deleting them, to avoid iterator invalidation.

  • Utilizing DSU: The process of multiple initial values converging to the same value is represented using unite operations.

  • 1-indexed vs 0-indexed: Be careful with index handling to match the BIT and the input value range (\(1 \dots K\)).

    Source Code

#include <iostream>
#include <vector>
#include <numeric>
#include <set>
#include <algorithm>

using namespace std;

/**
 * Disjoint Set Union (DSU) to keep track of initial values that map to the same current value.
 */
struct DSU {
    vector<int> parent;
    vector<int> rnk;
    DSU(int n) {
        parent.resize(n + 1);
        iota(parent.begin(), parent.end(), 0);
        rnk.resize(n + 1, 0);
    }
    int find(int i) {
        if (parent[i] == i) return i;
        return parent[i] = find(parent[i]);
    }
    int unite(int i, int j) {
        int root_i = find(i);
        int root_j = find(j);
        if (root_i != root_j) {
            if (rnk[root_i] < rnk[root_j]) {
                parent[root_i] = root_j;
                return root_j;
            } else {
                parent[root_j] = root_i;
                if (rnk[root_i] == rnk[root_j]) rnk[root_i]++;
                return root_i;
            }
        }
        return root_i;
    }
};

/**
 * Fenwick Tree (Binary Indexed Tree) for range distinct value queries.
 */
struct FenwickTree {
    int n;
    vector<int> tree;
    FenwickTree(int n) : n(n), tree(n + 1, 0) {}
    void add(int i, int delta) {
        for (; i <= n; i += i & -i) tree[i] += delta;
    }
    int query(int i) {
        int sum = 0;
        for (; i > 0; i -= i & -i) sum += tree[i];
        return sum;
    }
};

int main() {
    // Fast I/O
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int N, K, Q;
    if (!(cin >> N >> K >> Q)) return 0;

    vector<int> L(N), R(N), X(N);
    for (int i = 0; i < N; ++i) {
        cin >> L[i] >> R[i] >> X[i];
    }

    // Set of currently active values in the transformer
    set<int> S;
    // Mapping from a value to the root of its DSU component
    vector<int> pos(K + 1, -1);
    // Mapping from the root of a DSU component to its current value
    vector<int> current_value(K + 1);
    DSU dsu(K);

    // Initial state: each value s from 1 to K is its own component with value s
    for (int v = 1; v <= K; ++v) {
        S.insert(v);
        pos[v] = v;
        current_value[v] = v;
    }

    // Process each transformation step
    for (int i = 0; i < N; ++i) {
        auto it = S.lower_bound(L[i]);
        vector<int> to_merge;
        while (it != S.end() && *it <= R[i]) {
            to_merge.push_back(*it);
            it++;
        }
        if (to_merge.empty()) continue;

        int target_v = X[i];
        int root_of_target;

        if (S.count(target_v)) {
            root_of_target = dsu.find(pos[target_v]);
        } else {
            // target_v is not currently active, so move one component to target_v
            int v0 = to_merge.back();
            to_merge.pop_back();
            root_of_target = dsu.find(pos[v0]);
            S.erase(v0);
            pos[v0] = -1;
            S.insert(target_v);
            pos[target_v] = root_of_target;
            current_value[root_of_target] = target_v;
        }

        // Merge all other components in the range [L_i, R_i] into the target_v component
        for (int v : to_merge) {
            if (v == target_v) continue;
            int root_v = dsu.find(pos[v]);
            root_of_target = dsu.unite(root_v, root_of_target);
            S.erase(v);
            pos[v] = -1;
            pos[target_v] = root_of_target;
            current_value[root_of_target] = target_v;
        }
    }

    // Compute the final value F(s) for each initial signal s
    vector<int> F(K);
    for (int s = 1; s <= K; ++s) {
        F[s - 1] = current_value[dsu.find(s)];
    }

    // Answer range distinct queries offline using a Fenwick tree
    vector<vector<pair<int, int>>> queries_at(K + 1);
    for (int j = 0; j < Q; ++j) {
        int a, b;
        cin >> a >> b;
        queries_at[b].push_back({a, j});
    }

    vector<int> last_pos(K + 1, 0);
    FenwickTree ft(K);
    vector<int> results(Q);
    for (int i = 1; i <= K; ++i) {
        int val = F[i - 1];
        if (last_pos[val] != 0) {
            // Remove the contribution of the previous occurrence of this value
            ft.add(last_pos[val], -1);
        }
        // Add the contribution of the current occurrence
        ft.add(i, 1);
        last_pos[val] = i;

        // Process all queries ending at the current position i
        for (auto& q : queries_at[i]) {
            int left = q.first;
            int idx = q.second;
            // The number of distinct values in F[left-1...i-1] is the number of values
            // whose last occurrence in F[0...i-1] is at an index in [left-1, i-1].
            results[idx] = ft.query(i) - ft.query(left - 1);
        }
    }

    // Print the results for each query
    for (int j = 0; j < Q; ++j) {
        cout << results[j] << "\n";
    }

    return 0;
}

This editorial was generated by gemini-3-flash-thinking.

posted:
last update: