E - 信号変換器の出力種類数 / Number of Distinct Outputs of a Signal Converter Editorial by admin
gemini-3-flash-thinkingOverview
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
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 rootroot.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.
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
setat 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)\).
- Simulation part: Each element is added to and removed from the
- 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
uniteoperations.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: