很好的一道根號題。
題意
給定一個序列 \(a_1, a_2, \cdots, a_n\) 以及 \(m\) 個集合 \(S_1, S_2, \cdots, S_m\)。每個集合中的元素都代表序列 \(a\) 中的一個下標。
現在有 \(q\) 個詢問,每個詢問有兩種型別:
-
+ k x
,表示將所有 \(a_{S_{k, i}}\) 加上 \(x\)。 -
? k
,表示查詢 \(\sum\limits_{i} a_{S_{k, i}}\)。
\(n, m, q, \sum |S_k| \le 10^5\)
題解
首先這個詢問型別(只有更改和更改)挺像操作分塊,那麼往這方面想一想。
當從一個塊跳到下一個塊,需要重建 \(a\) 序列,這個並不難做,只需要記一個 \(tag_i\) 表示 \(S_i\) 被加上了多少就行了。考慮塊內的操作對詢問的影響,操作 + i x
對詢問 ? j
的影響是讓 \(j\) 的答案加上了 \(x \cdot \left(|S_i \cup S_j|\right)\),那麼問題轉化成了怎麼 \(O(1)\) 回答 \(\left(|S_i \cup S_j|\right)\) 的大小。
這個東西肯定是需要預處理的,考慮根號分治,即將集合分為兩類(假設 \(n, m, q, \sum |S_k|\) 同階):\(|S_k| > \sqrt{n}\) 為大集合,\(|S_k| \le \sqrt{n}\) 的是小集合,預處理每個大集合與其他集合的交集大小,資訊量是 \(O(n\sqrt{n})\) 的,可以存下。接著在詢問的時候,考慮塊內的操作 + i x
對當前詢問 ? j
的影響,分為 \(2\) 類情況:
-
\(S_i\) 或者 \(S_j\) 中的一個是大集合,可以直接回答交集大小。
-
\(S_i\) 和 \(S_j\) 都是小集合,因為沒存它們的資訊,不能直接回答,特殊處理一下。因為有 \(S_i, S_j \le \sqrt{n}\) 的條件,所以可以新開一個陣列 \(b_1, b_2, \cdots, b_n\) 表示當前塊內小集合的修改,修改 \(S_i\) 的時候將 \(b_{S_{i, k}}\) 加上 \(x\),在查詢 \(S_j\) 的時候查詢 \(\sum b_{S_{j, k}}\) 就能得到小集合之間的貢獻了。
總計時間複雜度 \(O(n \sqrt{n})\)。
(好像有不用操作分塊的做法,時間複雜度都也是 \(O(n \sqrt{n})\),可能更好寫,可以學習一下)
code:
#include <bits/stdc++.h>
#define vi vector <int>
#define vl vector <i64>
#define pii pair <int, int>
#define pll pair <i64, i64>
#define mp make_pair
#define tpi tuple <int, int, int>
#define tpl tuple <i64, i64, i64>
#define mt make_tuple
#define eb emplace_back
#define pb push_back
#define ft first
#define sd second
#define bg begin()
#define ed end()
#define mx_ele max_element
#define mn_ele min_element
#define sz(x) (int)(x.size())
#define alls(x) x.bg, x.ed
#define rep(i, l, r) for (int i = (l); i <= (int)(r); ++i)
#define re(i, l, r) rep(i, (l), (r) - 1)
#define per(i, r, l) for (int i = (r); i >= (int)(l); --i)
#define er(i, r, l) per(i, (r) - 1, (l))
#define repp(i, v) for (auto i : v)
#define i64 long long
#define ld long double
using namespace std;
const int inf = 1E9;
const i64 INF = 1E15;
// #define int i64
const int N = 1E5 + 5;
int n, m, q, sL, L, pos[N];
i64 a[N], b[N], tag[N], sum[N];
vi S[N], d[N], id;
vector <pair <int, int>> v;
void rebuild() {
rep(i, 1, m) tag[i] = 0;
for (auto [k, x] : v) tag[k] += x;
v.clear();
rep(i, 1, n) b[i] = 0;
rep(i, 1, m) {
for (auto j : S[i]) a[j] += tag[i];
}
re(i, 0, sz(id)) {
int j = id[i]; sum[i] = 0;
for (auto p : S[j]) sum[i] += a[p];
}
}
signed main(void) {
ios :: sync_with_stdio(false);
cin.tie(nullptr); cout.tie(nullptr);
cin >> n >> m >> q;
rep(i, 1, n) cin >> a[i];
rep(i, 1, m) {
int k; cin >> k; L += k;
while (k--) {int x; cin >> x; S[i].eb(x);}
sort(alls(S[i]));
} sL = sqrt(L);
rep(i, 1, m) if (sz(S[i]) >= sL) id.eb(i);
vi vis(n + 1);
rep(i, 1, m) d[i].resize(sz(id));
re(i, 0, sz(id)) {
fill(alls(vis), 0);
int j = id[i];
for (auto p : S[j]) vis[p] = 1;
rep(k, 1, m)
d[k][i] = count_if(alls(S[k]), [&](auto x) {return vis[x];});
pos[j] = i;
}
rebuild();
int bl = sqrt(q);
rep(i, 1, q) {
char opt; int k; cin >> opt >> k;
if (opt == '+') {
int x; cin >> x; v.eb(k, x);
if (sz(S[k]) < sL) {
for (auto p : S[k])
b[p] += x;
}
} else {
i64 ans = 0;
if (sz(S[k]) >= sL) {
int pid = pos[k]; ans = sum[pid];
for (auto [i, x] : v) {
ans += 1LL * x * d[i][pid];
}
} else {
for (auto p : S[k])
ans += b[p] + a[p];
for (auto [i, x] : v) {
if (sz(S[i]) < sL) continue;
ans += 1LL * x * d[k][pos[i]];
}
}
cout << ans << '\n';
}
if ((sz(v) + 1) == bl) rebuild();
}
}