CF348C 題解

CTHOOH發表於2024-04-10

很好的一道根號題。

題意

給定一個序列 \(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();
  }
}