題意
多組資料。數軸上有 \(n\) 個點,編號為 \(1 \sim n\),對這些點做 $ m $ 次操作。
每次操作給出三個整數 \(a_i(1 \le a_i \le n) \ \ \ d_i(1 \le d_i \le 10) \ \ \ k_i(0 \le k_i \le n)\)。將點 \(a_i, a_i + d_i, a_i + 2 \times d_i, a_i + 3 \times d_i, \cdot\cdot\cdot, a_i + k_i \times d_i\)兩兩相連。問做完所有的操作之後,有多少個連通塊。
資料範圍:\(\Sigma \ n \le 2 \times 10^5, \Sigma \ m \le 2 \times 10^5\)。
題解
對於連通塊相關的問題,通常考慮用並查集
合併。但是如果暴力合併的話,時間複雜度為 \(\mathcal{O}(n m)\),顯然透過不了這道題。
仔細觀察發現,\(d\) 的上界
很小,只有 \(10\)。不妨對這些操作按照 \(d\) 的大小分組,然後每一組單獨考慮。
對於合併操作,我們只需要標記一下,哪些點需要和前面(或後面)的鄰點合併,最後再統一處理。
對於標記,我們可以差分
處理。
時間複雜度為 \(\mathcal{O}(d \times n)\)。
點選檢視程式碼
#include <cstdio>
#include <iostream>
using i64 = long long;
inline int read() {
int res = 0; bool sym = false; char ch = getchar();
while (ch < '0' || ch > '9') sym |= ch == '-', ch = getchar();
while (ch >= '0' && ch <= '9') res = (res << 3) + (res << 1) + (ch & 15), ch = getchar();
return sym ? -res : res;
}
constexpr int N = 2e5 + 50, M = 10;
int T, n, m, a, d, k, tag[M + 1][N], par[N], ans;
int find(int x) {
return par[x] == x ? x : par[x] = find(par[x]);
}
void merge(int x, int y) {
x = find(x); y = find(y); if (x != y) par[y] = x;
}
void solve() {
n = read(); m = read(); ans = 0;
for (int i = 1; i <= n; i++) {
par[i] = i;
}
for (int d = 1; d <= M; d++) {
for (int i = 1; i <= n; i++) {
tag[d][i] = 0;
}
}
for (int i = 1; i <= m; i++) {
a = read(); d = read(); k = read();
if (k) {
// 差分
tag[d][a + d]++;
tag[d][a + (k + 1) * d]--;
}
}
for (int d = 1; d <= M; d++) {
for (int i = 1; i <= n; i++) {
if (i - d >= 1) {
tag[d][i] += tag[d][i - d];
}
}
}
for (int d = 1; d <= M; d++) {
for (int i = 1; i <= n; i++) {
if (tag[d][i]) {
merge(i, i - d);
}
}
}
for (int i = 1; i <= n; i++) {
if (find(i) == i) {
ans++;
}
}
printf("%d\n", ans);
}
int main() {
for (T = read(); T; T--) {
solve();
}
return 0;
}