DS 记录

前言

题目是乱序的。因为太菜了不会卡常,所以很多题是九十几分。

UPD. 2022.09.19

改名为 DS 记录

UPD. 2022.04.27

发现很多以前写的懒得写题解,感觉又都不太会了,后面还是做一题写一题比较好。

发现刷 Ynoi 收益很小,大部分时间只是在卡常。

不过数据结构题写起来有意思,自己写起来乐在其中,可能刷 Ynoi 对于算法竞赛并没有什么用,更多只是自我满足罢了。

P5047 [Ynoi2019 模拟赛] Yuno loves sqrt technology II

给定序列 \(a_n\), \(m\) 次询问区间逆序对。

\(1 \leq n, m \leq 10^5, 1 \leq a_i \leq 10^9\)

莫队二次离线。

求前缀比它小/大的数的个数,用 \(O(\sqrt n)\) 修改,\(O(1)\) 查询的搞一下就行。

#include <bits/stdc++.h>

constexpr int T(512), N(1e5 + 5), S(N / T + 1);
struct Qry {
  int l, r, id;
  bool operator<(const Qry &rhs) const {
    return l / T == rhs.l / T ? l / T & 1 ? r < rhs.r : r > rhs.r : l < rhs.l;
  }
};
int n, m;

int a[N], s[S];
void clear() {
  memset(a, 0, sizeof a);
  memset(s, 0, sizeof s);
}
void add(int p) {
  int t = p / T;
  for (int i = t * T; i <= p; i++) a[i]++;
  for (int i = 0; i < t; i++) s[i]++;
}
int ask(int p) {
  p = std::max(p, 0);
  return a[p] + s[p / T];
}


int main() {
  std::ios::sync_with_stdio(0);
  std::cin.tie(0);
  std::cin >> n >> m;
  std::vector<int> a(n);
  for (int &x : a) std::cin >> x;
  auto b = a;
  std::sort(b.begin(), b.end());
  b.resize(std::unique(b.begin(), b.end()) - b.begin());
  for (int &x : a) x = std::lower_bound(b.begin(), b.end(), x) - b.begin();
  std::vector<long long> c(n + 1), d(n + 1);
  
  for (int i = 0; i < n; i++) {
    add(a[i]);
    c[i + 1] = c[i] + ask(a[i + 1] + 1);
    d[i + 1] = d[i] + i + 1 - ask(a[i]);
  }
  std::vector<Qry> q(m);
  for (int i = 0; i < m; i++) {
    std::cin >> q[i].l >> q[i].r;
    q[i].l--, q[i].r--;
    q[i].id = i;
  }
  std::sort(q.begin(), q.end());
  std::vector<long long> ans(m), out(m);
  std::vector<std::vector<std::array<int, 3>>> g(n), h(n);
  for (int i = 0, x = 1, y = 0; i < m; i++) {
    if (y != q[i].r) {
      ans[i] += c[q[i].r] - c[y];
      if (x) y < q[i].r ? g[x - 1].push_back({y + 1, q[i].r, -i - 1}) : g[x - 1].push_back({q[i].r + 1, y, i});
      y = q[i].r;
    }
    if (x != q[i].l) {
      ans[i] += d[q[i].l] - d[x];
      x < q[i].l ? h[y].push_back({x, q[i].l - 1, -i - 1}) : h[y].push_back({q[i].l, x - 1, i});
      x = q[i].l;
    }
  }
  clear();
  for (int i = 0; i < n; i++) {
    add(a[i]);
    for (auto [l, r, id] : g[i]) {
      int c = id < 0 ? id = -id - 1, -1 : 1;
      for (int j = l; j <= r; j++) {
    ans[id] += c * ask(a[j] + 1);
      }
    }
    for (auto [l, r, id] : h[i]) {
      int c = id < 0 ? id = -id - 1, -1 : 1;
      for (int j = l; j <= r; j++) {
    ans[id] += c * (i + 1 - ask(a[j]));
      }
    }
  }
  for (int i = 1; i < m; i++) ans[i] += ans[i - 1];
  for (int i = 0; i < m; i++) out[q[i].id] = ans[i];
  for (auto x : out) std::cout << x << "\n";
  return 0;
}

P5046 [Ynoi2019 模拟赛] Yuno loves sqrt technology I

给定 \(1\dots n\)排列 \(a_n\), \(m\) 次询问区间逆序对。

\(1 \leq n, m \leq 10^5\)

强制在线。

序列分块,块内预处理前缀后缀逆序对,每块预处理该块和原序列前缀的逆序对。

块内用两个前缀相减,再归并计算两个前缀之间的贡献。

整块直接用预处理的计算,然后用归并计算两个散块之间的贡献。

\(O(n \sqrt n)\)

// Author:  HolyK
// Created: Tue Sep 14 14:01:15 2021
#include <bits/stdc++.h>
template <class T, class U>
inline bool smin(T &x, const U &y) {
  return y < x ? x = y, 1 : 0;
}
template <class T, class U>
inline bool smax(T &x, const U &y) {
  return x < y ? x = y, 1 : 0;
}

using LL = long long;
using PII = std::pair<int, int>;
inline char gc() {
  static constexpr int BufferSize = 1 << 22 | 5;
  static char buf[BufferSize], *p, *q;
  static std::streambuf *i = std::cin.rdbuf();
  return p == q ? p = buf, q = p + i->sgetn(p, BufferSize), p == q ? EOF : *p++ : *p++;
}
struct Reader {
  template <class T>
  Reader &operator>>(T &w) {
    char c, p = 0;
    for (; !std::isdigit(c = gc());) if (c == '-') p = 1;
    for (w = c & 15; std::isdigit(c = gc()); w = w * 10 + (c & 15)) ;
    if (p) w = -w;
    return *this;
  }
} cin;

constexpr int N(1e5 + 5), T(512);
LL cal(PII *a, PII *b, int l1, int r1, PII *c, PII *d, int l2, int r2) {
  LL res = 0;
  for (int now = 0; a < b; a++) {
    if (a->second < l1 || a->second >= r1) continue;
    for (; c < d && *c < *a; c++) {
      if (c->second < l2 || c->second >= r2) continue;
      now++;
    }
    res += now;
  }
  return res;
}

int n, m, a[N];
PII b[N], bb[N];
int c[N];
void add(int p, int x) {
  for (; p <= n; p += p & -p) c[p] += x;
}
int ask(int p) {
  int r = 0;
  for (; p; p -= p & -p) r += c[p];
  return r;
}
constexpr int S((N - 1 + T) / T);
LL pre[N], suf[N], cnt[S][N];

int main() {
  // freopen("t.in", "r", stdin);
  // freopen(".out", "w", stdout);
  std::ios::sync_with_stdio(false);
  std::cin.tie(nullptr);
  cin >> n >> m;

  for (int i = 0; i < n; i++) {
    cin >> a[i];
    bb[i] = b[i] = {a[i], i};
  }
  std::sort(bb, bb + n);
  for (int l = 0, o = 0; l < n; l += T, o++) {
    int r = std::min(n, l + T);
    std::sort(b + l, b + r);
    
    for (int i = 0, j = l; i < n || j < r;) {
      if (j == r || i < n && bb[i] < b[j]) {
        cnt[o][bb[i].second] += j - l;
        i++;
      } else {
        j++;
      }
    }
    for (int i = 1; i < n; i++) cnt[o][i] += cnt[o][i - 1];
    
    for (int i = l; i < r; i++) {
      pre[i] = i - l - ask(a[i]);
      add(a[i], 1);
    }
    for (int i = l; i < r; i++) {
      add(a[i], -1);      
    }
    for (int i = r - 1; i >= l; i--) {
      suf[i] = ask(a[i]);
      add(a[i], 1);
    }
    for (int i = l; i < r; i++) {
      add(a[i], -1);
    }
    
    for (int i = l + 1; i < r; i++) pre[i] += pre[i - 1];
    for (int i = r - 2; i >= l; i--) suf[i] += suf[i + 1];
  }
  LL ans = 0;
  while (m--) {
    LL l, r;
    cin >> l >> r;
    l ^= ans, r ^= ans;
    l--, r--;
    int bl = l / T, br = r / T;
    if (bl == br) {
      ans = pre[r] - (l == bl * T ? 0 : pre[l - 1]);
      int x = bl * T, y = std::min(n, x + T);
      ans -= cal(b + x, b + y, x, l, b + x, b + y, l, r + 1);
    } else {
      ans = suf[l] + pre[r];
      ans += std::max(0, (br - bl - 1) * T) * (r - br * T + 1LL);
      
      for (int i = bl + 1; i < br; i++) {
        ans += suf[i * T];
        ans += cnt[i][i * T - 1];
        if (l) ans -= cnt[i][l - 1];
        ans -= cnt[i][r] - cnt[i][br * T - 1];
      }
      // std::cerr << ans << "\n";
      ans += cal(b + bl * T, b + bl * T + T, l, bl * T + T,
                 b + br * T, b + std::min(n, br * T + T), br * T, r + 1);
    }
    std::cout << ans << "\n";
    // break;
  }
  
  return 0;
}

P5048 [Ynoi2019 模拟赛] Yuno loves sqrt technology III

给定序列 \(a_n\), \(m\) 次询问区间众数出现的次数。

\(1 \leq n, m \leq 10^5, 1 \leq a_i \leq 10^9\)

强制在线。

区间众数要么是中间的整块的众数,要么是两边散块的。

预处理整块两两之间区间的众数个数,然后用 vector 存每个数出现的位置。

对于散块每个数,直接看它的位置 +ans 之后还在不在询问区间内,在的话就暴力拓展。拓展次数是 \(O(\sqrt n)\) 级别。

复杂度 \(O(n \sqrt n)\)

// Author:  HolyK
// Created: Tue Sep 14 17:41:10 2021
#include <bits/stdc++.h>
template <class T, class U>
inline bool smin(T &x, const U &y) {
  return y < x ? x = y, 1 : 0;
}
template <class T, class U>
inline bool smax(T &x, const U &y) {
  return x < y ? x = y, 1 : 0;
}

using LL = long long;
using PII = std::pair<int, int>;

constexpr int N(5e5 + 5), T(512), S((N - 1 + T) / T);
int n, m, a[N], b[N], c[N], pos[N], cnt[S][S];
struct LazyVector {
  std::vector<int> a, b;
  int c;
  LazyVector(int n) : a(n), b(n), c(0) {}
  void clear() { c++; }
  int &operator[](const int &i) {
    if (b[i] != c) b[i] = c, a[i] = 0;
    return a[i];
  }
};
int main() {
  std::ios::sync_with_stdio(false);
  std::cin.tie(nullptr);
  std::cin >> n >> m;
  for (int i = 0; i < n; i++) {
    std::cin >> a[i];
    b[i] = a[i];
  }
  std::sort(b, b + n);
  int t = std::unique(b, b + n) - b;
  for (int i = 0; i < n; i++) {
    a[i] = std::lower_bound(b, b + t, a[i]) - b;
    c[a[i]]++;
  }
  for (int i = 1; i < t; i++) c[i] += c[i - 1];
  for (int i = n - 1; i >= 0; i--) {
    b[--c[a[i]]] = i;
    pos[i] = c[a[i]];
  }
  LazyVector v(t);
  for (int i = 0; i < n; i += T) {  
    int max = 0;
    v.clear();
    for (int j = i + T; j < n; j += T) {
      for (int k = j - T; k < j; k++) 
        smax(max, ++v[a[k]]);
      cnt[i / T][j / T] = max;
    }
  }
  int ans = 0;
  while (m--) {
    int l, r;
    std::cin >> l >> r;
    l ^= ans, r ^= ans;
    l--, r--;
    int bl = l / T, br = r / T;
    if (bl == br) {
      ans = 0;
      v.clear();
      for (int i = l; i <= r; i++) {
        smax(ans, ++v[a[i]]);
      }
    } else {
      ans = cnt[bl + 1][br];
      int y = bl * T + T, x = br * T;
      for (int i = l; i < y; i++) {
        while (pos[i] + ans < n && b[pos[i] + ans] <= r && a[b[pos[i] + ans]] == a[i]) ans++;
      }
      for (int i = x; i <= r; i++) {
        while (pos[i] - ans >= 0 && b[pos[i] - ans] >= l && a[b[pos[i] - ans]] == a[i]) ans++;
      }
    }
    std::cout << ans << "\n";
  }
  return 0;
}

P7447 [Ynoi2007] rgxsxrs

给定一个序列 \(a_n\)\(m\) 次操作:

区间 \([l, r]\) 大于 \(x\) 的数减 \(x\);求区间和、最大最小值。

\(n, m \leq 5 \times 10^5, 1 \leq a_i, x \leq 10^9\)

空间限制 64MB,强制在线。

值域倍增分块,\([b^k, b^{k+1})\) 在一个块里,所以有 \(\log_b a\) 个块。每一块开一个线段树。

修改时,若 \(x < b^k\),则块内所有数都要减 \(x\),有些数被减后会掉落到更小的值域块中,掉落操作会发生 \(n \log_b a\) 次,每次需要 \(\log n\) 的时间定位和修改,均摊下来还是 \(O(n\log_b a \log n)\)

\(b^k \leq x < b^{k+1}\),则块内有些数需要减 \(x\)。因为 \(x \ge b^k\),所以每个数最多减 \(b\) 次就会掉落,所以每个数最多会被减 \(b\log_b a\) 次,最坏情况下每次需要 \(\log n\) 时间定位,所以是 \(O(nb\log_b a \log n)\)

\(x \ge b^{k+1}\) 则什么都不用做。

\(n, m\) 同阶,时间复杂度是 \(O(nb \log n \log_b a)\),空间复杂度是 \(O(n \log_b a)\),取 \(b = 2\) 时间最优,但是空间炸了。

考虑先将原序列分块,块大小 \(B = \log_b a\),然后线段树节点数变为 \(O(n / B)\),这样线段树定位和修改的复杂度变成了 \(O(\log n + B)\),仅仅增加了一点常数。

在实现时,要根据实际情况选取 \(b, B\) 的大小,为了 cache 友好,不写 \(\log_b a\) 棵线段树,而是每个线段树维护 \(O(\log_b a)\) 的信息。

// Author:  HolyK
// Created: Fri Aug 27 21:50:45 2021
#include <bits/stdc++.h>
template <class T, class U>
inline bool smin(T &x, const U &y) {
  return y < x ? x = y, 1 : 0;
}
template <class T, class U>
inline bool smax(T &x, const U &y) {
  return x < y ? x = y, 1 : 0;
}
inline char gc() {
  static constexpr int BufferSize = 1 << 22 | 5;
  static char buf[BufferSize], *p, *q;
  static std::streambuf *i = std::cin.rdbuf();
  return p == q ? p = buf, q = p + i->sgetn(p, BufferSize), p == q ? EOF : *p++ : *p++;
}
struct Reader {
  template <class T>
  Reader &operator>>(T &w) {
    char c, p = 0;
    for (; !std::isdigit(c = gc());) if (c == '-') p = 1;
    for (w = c & 15; std::isdigit(c = gc()); w = w * 10 + (c & 15)) ;
    if (p) w = -w;
    return *this;
  }
} cin;

using LL = long long;
using PII = std::pair<int, int>;
using u64 = uint64_t;
using u32 = uint32_t;
constexpr int N(5e5), T(8), P(32 / T), S(1 << P), B(32), L((N + B - 1) / B);
int n, m, k;
u32 a[N];
u64 pw[T + 1];
struct Info {
  u32 min, max, siz;
  u64 sum;
  Info &operator+=(const Info &r) {
    smin(min, r.min);
    smax(max, r.max);
    siz += r.siz;
    sum += r.sum;
    return *this;
  }
  void tag(const u32 &x) {
    min -= x, max -= x, sum -= u64(siz) * x;
  }
  void ins(const u32 &x) {
    smin(min, x), smax(max, x), siz++, sum += x;
  }
} t[L * 4][T];
u32 tag[L * 4][T];
u32 pos(u32 x) { return std::__lg(x) / P; }
constexpr Info Null = {~0U, 0U, 0U, 0ULL};
inline Info operator+(const Info &a, const Info &b) { return {std::min(a.min, b.min), std::max(a.max, b.max), a.siz + b.siz, a.sum + b.sum}; }
#define ls o << 1
#define rs o << 1 | 1
#define APPLY() for (int _ = 0; _ < T; _ += 4) F(_), F(_ + 1), F(_ + 2), F(_ + 3)
void pushup(int o) {
#define F(i) t[o][i] = t[ls][i] + t[rs][i]
  APPLY();
#undef F
}
void add(int o, int i, u32 z) {
  if (t[o][i].siz) {
    t[o][i].tag(z);
    tag[o][i] += z;
  }
}
void pushdown(int o) {
#define F(i) (tag[o][i] ? (add(ls, i, tag[o][i]), add(rs, i, tag[o][i]), tag[o][i] = 0) : 1)
  APPLY();
#undef F
}
u32 max, min;
u64 sum;
void ask(int o, int l, int r, int x, int y) {
  if (r - l == 1) {
    for (int i = l * B; i < r * B && i < n; i++) {
      a[i] -= tag[o][pos(a[i])];
    }
    memset(tag[o], 0, sizeof tag[o]);
    for (int i = x; i < y; i++) {
      smax(max, a[i]);
      smin(min, a[i]);
      sum += a[i];
    }
    return;
  }
  if (x <= l * B && r * B <= y) {
#define F(i) (smax(max, t[o][i].max), smin(min, t[o][i].min), sum += t[o][i].sum)
    APPLY();
#undef F
    // std::cerr << "range-ask: " << l << " " << r << " " << sum - pre << " ";    
    return;
  } 
  pushdown(o);
  int m = l + r >> 1;
  if (y <= m * B) {
    ask(ls, l, m, x, y);
  } else if (x >= m * B) {
    ask(rs, m, r, x, y);
  } else {
    ask(ls, l, m, x, m * B);
    ask(rs, m, r, m * B, y);
  }
}
void update(int o, int l, int r, int x, int y, u32 z, int bitl = 0, int bitr = T - 1) {

  while (bitl <= bitr && t[o][bitl].max <= z) bitl++;
  while (bitl <= bitr && t[o][bitr].max <= z) bitr--;
  if (bitl > bitr) return;
  if (r - l == 1) {
    for (int i = l * B; i < r * B && i < n; i++) {
      a[i] -= tag[o][pos(a[i])];
    }
    memset(tag[o], 0, sizeof tag[o]);
    // std::cerr << "update-single: ";
    for (int i = x; i < y; i++) {
      if (a[i] > z && a[i] < pw[bitr + 1]) {
        // std::cerr << i << " ";
        a[i] -= z;
      }
    }
    // std::cerr << "\n";
    std::fill(t[o], t[o] + T, Null);
    for (int i = l * B; i < r * B && i < n; i++) {
      t[o][pos(a[i])].ins(a[i]);
    } 
    return;
  }
  if (x <= l * B && r * B <= y) {
    for (; bitl <= bitr; bitr--) {
      const int &i = bitr;
      if (t[o][i].min > z && t[o][i].min - z >= pw[i]) {
        add(o, i, z);
        continue;
      }
      break;
    }
    if (bitl > bitr) {
      return;
    }
  }
  
  pushdown(o);
  int m = l + r >> 1;
  if (y <= m * B) {
    update(ls, l, m, x, y, z, bitl, bitr);
  } else if (x >= m * B) {
    update(rs, m, r, x, y, z, bitl, bitr);
  } else {
    update(ls, l, m, x, m * B, z, bitl, bitr);
    update(rs, m, r, m * B, y, z, bitl, bitr);
  }
  pushup(o);

}
void build(int o, int l, int r) {
  if (r - l == 1) {
    std::fill(t[o], t[o] + T, Null);
    int x = l * B, y = std::min(r * B, n);
    for (int i = x; i < y; i++) {
      t[o][pos(a[i])].ins(a[i]);
    }
    return;
  }
  int m = l + r >> 1;
  build(ls, l, m), build(rs, m, r);
  pushup(o);
}
int main() {
  // freopen("tt.in", "r", stdin);
  // freopen("t.out", "w", stdout);
  std::ios::sync_with_stdio(false);
  std::cin.tie(nullptr);
  pw[0] = 1;
  for (int i = 1; i <= T; i++) pw[i] = pw[i - 1] * S;
  cin >> n >> m;
  for (int i = 0; i < n; i++) cin >> a[i];
  k = (n + B - 1) / B;
  std::cerr << "k = " << k << "\n";
  build(1, 0, k);
  int ans = 0;
  while (m--) {
    int opt, l, r;
    cin >> opt >> l >> r;
    l ^= ans, r ^= ans;
    assert(l <= r);
    l--;
    if (opt == 1) {
      u32 x;
      cin >> x;
      x ^= ans;
      update(1, 0, k, l, r, x);
    } else {
      max = 0U, min = ~0U, sum = 0ULL;
      ask(1, 0, k, l, r);
      ans = sum % (1 << 20);
      std::cout << sum << " " << min << " " << max << "\n";
    }
  }
  std::cerr << (double)clock() / CLOCKS_PER_SEC << "\n";
  return 0;
}

P6578 [Ynoi2019] 魔法少女网站

给定序列 \(a_n\)\(m\) 次操作,单点修改 \(a_x = y\),查询区间 \([l, r]\) 有多少子区间的最大值 \(\leq x\)

\(n, m \leq 3 \times 10^5, 1 \leq a_i \leq n\)

将操作分块,块大小为 \(B\)

对于每个操作块,被修改的位置只有 \(O(B)\) 个。没被修改的位置和询问按值从小到大排序,然后插入,用链表维护连续段。

每次询问,枚举前面的修改操作,暴力插入,询问完之后回滚。

由于是区间询问,还要考虑将序列分块,两边的散块暴力,完整的块要维护块内完整连续段的答案之和,以及左右两边不完整连续段的端点。

\(n, m\) 同阶,取 \(B = \sqrt n\),复杂度是 \(O(n \sqrt n)\)

// Author:  HolyK
// Created: Wed Jul 14 11:25:19 2021
#include <bits/stdc++.h>
#define dbg(a...) fprintf(stderr, a)
template <class T, class U>
inline bool smin(T &x, const U &y) {
  return y < x ? x = y, 1 : 0;
}
template <class T, class U>
inline bool smax(T &x, const U &y) {
  return x < y ? x = y, 1 : 0;
}

using LL = long long;
using PII = std::pair<int, int>;
inline char gc() {
  static constexpr int BufferSize = 1 << 22 | 5;
  static char buf[BufferSize], *p, *q;
  static std::streambuf *i = std::cin.rdbuf();
  return p == q ? p = buf, q = p + i->sgetn(p, BufferSize), p == q ? EOF : *p++ : *p++;
}
struct Reader {
  template <class T>
  Reader &operator>>(T &w) {
    char c, p = 0;
    for (; !std::isdigit(c = gc());) if (c == '-') p = 1;
    for (w = c & 15; std::isdigit(c = gc()); w = w * 10 + (c & 15)) ;
    if (p) w = -w;
    return *this;
  }
} cin;
// using std::cin;
template <class T>
struct Rec {
  std::vector<std::pair<T*, T>> rec;
  Rec(int n = 0) {
    rec.reserve(n);
  }
  void clear() { rec.clear(); }
  void operator()(T &x) {
    rec.emplace_back(&x, x);
  }
  void recover() {
    while (!rec.empty()) *rec.back().first = rec.back().second, rec.pop_back();
  }
};
constexpr int N(3e5 + 5), T(1024), S(N / T + 1);
struct Qry {
  int l, r, x, id;
  bool operator<(const Qry &rhs) const {
    return x < rhs.x || x == rhs.x && id < rhs.id;
  }
} q[N];
int n, m, tot;
PII a[N << 1];
int raw[N], val[N], b[N], pos[N], tmp[N];
LL f[N];
LL sum[S], ans[N];
int lp[S], rp[S], cnt[S];
bool vis[N];
struct Info {
  int l, r, x;
} rec[N];
int recs;
void ins(int l, int r, int z) {
  const int bl = l / T, br = r / T;
  if (bl == br) {
    sum[bl] += z * f[r - l + 1];
  } else if (z > 0) {
    rp[bl] = l, lp[br] = r;
  }
}
void add(int x) {
  if (b[x]) return;
  cnt[x / T]++;
  b[x] = 1;
  int l = x, r = x;
  if (x && b[x - 1]) {
    l = pos[x - 1];
    ins(pos[x - 1], x - 1, -1);
  }
  if (x + 1 < n && b[x + 1]) {
    r = pos[x + 1];
    ins(x + 1, pos[x + 1], -1);
  }
  pos[l] = r, pos[r] = l;
  ins(l, r, 1);
}
void add_rec(int x) {
  if (b[x]) return;
  cnt[x / T]++, b[x] = 1;
  int l = x, r = x;
  if (x && b[x - 1]) {
    l = pos[x - 1];
    ins(pos[x - 1], x - 1, -1);
  }
  if (x + 1 < n && b[x + 1]) {
    r = pos[x + 1];
    ins(x + 1, pos[x + 1], -1);
  }
  pos[l] = r, pos[r] = l;
  ins(l, r, 1);
  rec[++recs] = {l, r, x};
}
void recover() {
  // ri.recover(), rl.recover();
  while (recs) {
    auto &[l, r, x] = rec[recs--];
    cnt[x / T]--, b[x] = 0;
    ins(l, r, -1);
    if (l / T != r / T) rp[l / T] = lp[r / T] = -1;
    if (l < x) ins(l, x - 1, 1), pos[l] = x - 1, pos[x - 1] = l;
    if (x < r) ins(x + 1, r, 1), pos[r] = x + 1, pos[x + 1] = r;
  }
}

LL ask(int l, int r) {
  const int bl = l / T, br = r / T;
  LL ans = 0;
  if (bl == br) {
    int now = 0;
    for (int i = l; i <= r; i++) {
      if (b[i]) {
        now++;
      } else {
        ans += f[now];
        now = 0;
      }
    }
    return ans + f[now];
  }
  int y = bl * T + T, x = br * T, now = 0;
  for (int i = l; i < y; i++) {
    if (b[i]) {
      now++;
    } else {
      ans += f[now];
      now = 0;
    }
  }
  for (int i = bl + 1; i < br; i++, y += T) {
    if (T == cnt[i]) {
      now += T;
      continue;
    }
    if (~lp[i]) {
      now += lp[i] - y + 1;
    }
    ans += f[now] + sum[i];
    now = 0;
    if (~rp[i]) {
      now = y + T - rp[i];
    }
  }
  for (int i = x; i <= r; i++) {
    if (b[i]) {
      now++;
    } else {
      ans += f[now];
      now = 0;
    }
  }
  return ans + f[now];
};
int main() {
  f[0] = 0;
  for (int i = 1; i <= 3e5; i++) {
    f[i] = f[i - 1] + i;
  }
  double sta = (double)clock() / CLOCKS_PER_SEC;
#ifndef ONLINE_JUDGE
  freopen("t.in", "r", stdin);
  freopen("tt.out", "w", stdout);
#endif
  std::ios::sync_with_stdio(false);
  std::cin.tie(nullptr);
  cin >> n >> m, tot = (n - 1) / T + 1;
  
  for (int i = 0; i < n; i++) {
    cin >> raw[i];
    a[i] = {raw[i], i};
  }
  
  int ta = n, tq = 0;
  for (int i = 0; i < m; i++) {
    int opt;
    cin >> opt >> q[i].l >> q[i].r;
    q[i].l--;
    q[i].id = i;
    if (opt == 2) {
      cin >> q[i].x;
      q[i].r--;
      tq++;
    } else {
      a[ta++] = {q[i].r, q[i].l};
      ans[i] = -1;
    }
  }
  std::sort(a, a + ta);
  for (int i = 0, r, d; i < m; i = r) {
    for (; i < m && !q[i].x; i++) {
      raw[q[i].l] = q[i].r;;
    }
    r = std::min(m, i + 1324);
    // for (int k = step; r < m && k; r++) k -= !q[r].x;
    std::sort(q + i, q + r);
    for (d = i; d < r && !q[d].x; d++) ;
    // assert(d - i <= step);
    for (int j = 0; j < n; j++) {
      pos[j] = -1;
      b[j] = 0;
      vis[j] = 0;
      val[j] = raw[j];
    }
   for (int j = 0; j < tot; j++) {
      sum[j] = 0;
      cnt[j] = 0;
      lp[j] = rp[j] = -1;
    }
    for (int j = i; j < d; j++) {
      vis[q[j].l] = 1;
    }
    tmp[0] = 0;
    for (int j = 0; j < n; j++) if (vis[j]) tmp[++tmp[0]] = j;
    for (int j = d, k = 0; j < r; j++) {
      auto [ql, qr, x, id] = q[j];
      for (; k < ta && a[k].first <= x; k++) {
        if (val[a[k].second] != a[k].first || vis[a[k].second]) continue;
        add(a[k].second);
      }
      for (int t = i; t < d; t++) {
        if (q[t].id > id) continue;
        val[q[t].l] = q[t].r;
      }
      for (int t = 1; t <= tmp[0]; t++) {
        if (val[tmp[t]] <= x) add_rec(tmp[t]);
      }
      ans[id] = ask(ql, qr);
      for (int t = 1; t <= tmp[0]; t++) {
        val[tmp[t]] = raw[tmp[t]];
      }
      recover();
    }
    for (int j = i; j < d; j++) {
      raw[q[j].l] = q[j].r;
    }
  }
  for (int i = 0; i < m; i++) if (~ans[i]) std::cout << ans[i] << "\n";
  std::cerr << "time : " << (double)clock() / CLOCKS_PER_SEC - sta << "\n";
  return 0;
}

P5072 [Ynoi2015] 盼君勿忘

给定序列 \(a_n\)\(m\) 次询问给一个区间 \([l_i, r_i]\),对于它的每个子序列 \(S\),将 \(S\) 去重后求和,答案对 \(p_i\) 取模。

\(1 \leq n, m, a_i \leq 10^5, 1 \leq p \leq 10^9\)

莫队。

考虑每个元素对答案的贡献。设元素 \(x\) 的出现次数为 \(c\)\(len = r - l + 1\),那么贡献是 \(x(2^c-1)2^{len-c} = x2^{len} - x2^{len - c}\)。前者只需要维护不同数的和,后者考虑按照 \(c\) 根号分治。

  • \(c \geq \sqrt n\) 的数,不超过 \(\sqrt n\) 个,暴力计算。

  • \(c < \sqrt n\) 的,每个维护一下 \(\sum x\) 即可。

需要 \(O(1)\) 计算 \(p_i^k\),根号预处理 \(p^k, \big(p^{\sqrt n}\big)^k\) 即可。

// Author:  HolyK
// Created: Wed Sep  8 16:32:52 2021
#include <bits/stdc++.h>
template <class T, class U>
inline bool smin(T &x, const U &y) {
  return y < x ? x = y, 1 : 0;
}
template <class T, class U>
inline bool smax(T &x, const U &y) {
  return x < y ? x = y, 1 : 0;
}

using LL = long long;
using PII = std::pair<int, int>;

constexpr int N(1e5 + 5), T(384);
int n, m, a[N];
struct Qry {
  int l, r, p, id;
  bool operator<(const Qry &rhs) const {
    return l / T == rhs.l / T ? l / T & 1 ? r < rhs.r : r > rhs.r : l < rhs.l;
  }
} q[N];

int cnt[N];
LL s, sum[N];
void add(int x) {
  if (!cnt[x]) s += x;
  sum[cnt[x]] -= x;
  sum[++cnt[x]] += x;
}
void del(int x) {
  sum[cnt[x]] -= x;
  sum[--cnt[x]] += x;
  if (!cnt[x]) s -= x;
}
std::vector<int> big;
int ans[N], pw[T], ppw[T];
int main() {
  // freopen("t.in", "r", stdin);
  // freopen(".out", "w", stdout);
  std::ios::sync_with_stdio(false);
  std::cin.tie(nullptr);
  std::cin >> n >> m;
  for (int i = 1; i <= n; i++) {
    std::cin >> a[i];
    cnt[a[i]]++;
  }
  for (int i = 1; i <= 1e5; i++) {
    if (cnt[i] >= T) {
      big.push_back(i);
    }
  }
  for (int i = 1; i <= m; i++) {
    std::cin >> q[i].l >> q[i].r >> q[i].p;
    q[i].id = i;
  }
  std::sort(q + 1, q + 1 + m);
  memset(cnt, 0, sizeof cnt);
  for (int i = 1, x = 1, y = 0; i <= m; i++) {
    while (x > q[i].l) add(a[--x]);
    while (y < q[i].r) add(a[++y]);
    while (x < q[i].l) del(a[x++]);
    while (y > q[i].r) del(a[y--]);
    int len = q[i].r - q[i].l + 1;
    // std::cerr << "[Q] " << q[i].l << " " << q[i].r << ":\n";
    pw[0] = 1;
    const int &p = q[i].p;
    for (int i = 1; i < T; i++) pw[i] = 2LL * pw[i - 1] % p;
    ppw[0] = 1;
    ppw[1] = pw[T - 1] * 2LL % p;
    for (int i = 2; i < T; i++) ppw[i] = 1LL * ppw[i - 1] * ppw[1] % p;
    auto power = [&](int k) {
      return 1LL * ppw[k / T] * pw[k % T] % p;
    };
    auto &r = ans[q[i].id];
    // std::cerr << s << "\n";
    r = s % p * power(len) % p;
    for (int x : big) {
      if (cnt[x] >= T) {
        r = (r - 1LL * x * power(len - cnt[x])) % p;
      }
    }
    for (int i = std::min(T - 1, len), c = power(len - i); i; i--, c = 2LL * c % p) {
      if (sum[i]) {
        r = (r - sum[i] % p * c) % p;
      }
    }
    if (r < 0) r += p;
  }
  for (int i = 1; i <= m; i++) {
    std::cout << ans[i] << "\n";
  }
  return 0;
}

P5397 [Ynoi2018] 天降之物

给定序列 \(a_n\)\(m\) 次操作:将所有 \(x\) 改成 \(y\);找出 \((i, j)\) 满足 \(a_i = x, a_j = y\),求 \(\min |i - j|\)

所有数在 \([1, 10^5]\) 范围内,强制在线。

按照出现次数根号分治,大于等于 \(\sqrt n\) 的称为大,否则称为小。

如果没有修改,可以预处理大对其他值的答案 \(ans\),询问是有大就查表,否则就归并。

考虑修改,首先用并查集维护每个值的位置集合,每次修改值的时候只要合并并查集就行了,这样方便后面快速得到每个位置的值。

小值维护位置集合,小改为小时,如果合起来还是小,直接归并;否则产生了一个大,\(O(n)\) 处理它对其他值的答案。

小改为大(大改为小可以交换两个值)时,直接归并复杂度是 \(O(n)\),所以要再摊一下。

对于每个大值维护一个附属位置集合作为缓冲,小值位置集合先向大值附属集合归并,如果归并后大小 \(\geq \sqrt n\),则直接 \(O(n)\) 重构大值。

此时 \(ans\) 只维护大值非附属集合对其他值的答案,附属集合由于大小不超过 \(\sqrt n\),它产生的贡献在查询时可以直接归并查询。

复杂度 \(O(n \sqrt n)\)

// Author:  HolyK
// Created: Thu Sep 16 08:09:38 2021
#include <bits/stdc++.h>
template <class T, class U>
inline bool smin(T &x, const U &y) {
  return y < x ? x = y, 1 : 0;
}
template <class T, class U>
inline bool smax(T &x, const U &y) {
  return x < y ? x = y, 1 : 0;
}

using LL = long long;
using PII = std::pair<int, int>;

constexpr int N(1e5 + 5), T(512), S(N / T + 1);

template <int S>
struct LazyArray {
  int a[S], b[S], c;
  void clear() { c++; }
  int &operator[](const int &i) {
    if (b[i] != c) b[i] = c, a[i] = 0;
    return a[i];
  } 
};
int n, m, a[N], s[N];

std::vector<int> g[N], h[N];
int id[N], cnt, trash[N], *cur = trash, val[N], ans[S][N];
int newId() {
  return trash == cur ? ++cnt : *cur--;
}
void del(int x) {
  if (!id[x]) return;
  *++cur = id[x];
  memset(ans[id[x]], 0x3f, sizeof ans[id[x]]);
  val[id[x]] = 0;
  id[x] = 0;
}


int cal(const std::vector<int> &a, const std::vector<int> &b) {
  int res = 1e9;
  for (int i = 0, j = 0; i < a.size() || j < b.size(); ) {
    if (j == b.size() || i < a.size() && a[i] < b[j]) {
      if (j) smin(res, a[i] - b[j - 1]);
      i++;
    } else {
      if (i) smin(res, b[j] - a[i - 1]);
      j++;
    }
  }
  return res;
}

int fa[N], root[N];
int find(int x) {
  while (x != fa[x]) x = fa[x] = fa[fa[x]];
  return x;
}
void update(int x) {
  int y = id[x];
  for (int i = 1, p = 0; i <= n; i++) {
    a[i] = a[find(i)];
    if (a[i] == x) {
      p = i;
    } else if (p) {
      smin(ans[y][a[i]], i - p);
    }
  }
  for (int i = n, p = 0; i; i--) {
    if (a[i] == x) {
      p = i;
    } else if (p) {
      smin(ans[y][a[i]], p - i);
    }
  }
}
int main() {
  std::ios::sync_with_stdio(false);
  std::cin.tie(nullptr);
  std::cin >> n >> m;
  
  for (int i = 1; i <= n; i++) {
    std::cin >> a[i];
    s[a[i]]++;
  }

  for (int i = 1; i <= 1e5; i++) {
    if (s[i] >= T) {
      id[i] = newId();
      val[id[i]] = i;
    }
  }
  
  for (int i = 1; i <= n; i++) {
    if (s[a[i]] < T) g[a[i]].push_back(i);
    root[a[i]] = i;
  }

  for (int i = 1; i <= n; i++) {
    fa[i] = root[a[i]];
  }
  
  memset(ans, 0x3f, sizeof ans);
  for (int i = 1; i <= cnt; i++) {
    update(val[i]);
  }
  
  int res = 0;
  while (m--) {
    int opt, x, y;
    std::cin >> opt >> x >> y;
    x ^= res, y ^= res;
    static int tmp[N];
    
    auto mergeTo = [&](std::vector<int> &a, std::vector<int> &b) {
      std::merge(a.begin(), a.end(), b.begin(), b.end(), tmp);
      b.resize(a.size() + b.size());
      a.clear();
      std::copy_n(tmp, b.size(), b.begin());
    };
    // std::cerr << x << " " << y << " " << s[x] << " " << s[y] << "\n";
    if (opt == 1) {
      if (x == y || !s[x]) continue;
      if (!root[y]) {
        root[y] = root[x];
        a[root[y]] = y;
      } else {
        fa[root[x]] = root[y];
      }
      root[x] = 0;
      for (int i = 1; i <= cnt; i++) {
        smin(ans[i][y], ans[i][x]);
        ans[i][x] = 0x3f3f3f3f;
      }
      if (s[x] < T && s[y] < T) {
        s[y] += s[x], s[x] = 0;
        if (s[y] >= T) {
          id[y] = newId();
          val[id[y]] = y;
          g[x].clear(), g[y].clear();
          
          update(y);
          
        } else {
          mergeTo(g[x], g[y]);
        }
        continue;
      }
      
      if (s[x] >= T && s[y] < T || s[x] < T && s[y] >= T) {
        if (s[x] > s[y]) {
          std::swap(s[x], s[y]);
          std::swap(id[x], id[y]);
          std::swap(g[x], g[y]);
          std::swap(h[x], h[y]);
          val[id[y]] = y;
        }
        if (h[y].size() + s[x] < T) {
          s[y] += s[x], s[x] = 0;
          mergeTo(g[x], h[y]);
          continue;
        }
      }
      g[x].clear(), g[y].clear();
      h[x].clear(), h[y].clear(); 
      del(x);
      s[y] += s[x], s[x] = 0;
      update(y);
    } else {
      if (!s[x] || !s[y]) {
        res = 0;
        std::cout << "Ikaros\n";
        continue;
      }
      if (x == y) {
        res = 0;
        std::cout << "0\n";
        continue;
      }
      if (s[x] < T && s[y] < T) {
        res = cal(g[x], g[y]);
      } else if (s[x] >= T && s[y] >= T) {
        res = std::min(ans[id[x]][y], ans[id[y]][x]);
        smin(res, cal(h[x], h[y]));
      } else {
        if (s[x] < s[y]) std::swap(x, y);
        res = ans[id[x]][y];
        smin(res, cal(h[x], g[y]));
      }
      std::cout << res << "\n";
    }
  }
  return 0;
}

P5398 [Ynoi2018] GOSICK

给定序列 \(a_n\)\(m\) 次询问,给定区间 \([l, r]\),查询二元组 \((i, j)\) 的个数,满足 \(i, j \in [l, r], a_i \mid a_j\)

\(1 \leq n, m, a_i \leq 10^5\)

莫队二次离线。

加入或删除一个数,就查询该数在当前区间内的倍数和约数个数,显然可以通过差分搞成两个前缀之差。

发现枚举倍数复杂度炸了,所以把小数的贡献先搞掉。

\(T = \sqrt n\),计算每个 \(x < T\) 对每个询问的贡献。对于每个 \(x\),可以通过前缀和 \(O(n + m)\) 完成。

然后大数的因数和倍数都是 \(\sqrt n\) 级别的了,直接 \(\sqrt n\) 修改 \(O(1)\) 查询即可。

// Author:  HolyK
// Created: Sun May 30 14:48:18 2021
#include <bits/stdc++.h>
#define dbg(a...) fprintf(stderr, a)
template <class T, class U>
inline bool smin(T &x, const U &y) {
  return y < x ? x = y, 1 : 0;
}
template <class T, class U>
inline bool smax(T &x, const U &y) {
  return x < y ? x = y, 1 : 0;
}

using LL = long long;
using PII = std::pair<int, int>;
inline char gc() {
  static constexpr int BufferSize = 1 << 22 | 5;
  static char buf[BufferSize], *p, *q;
  static std::streambuf *i = std::cin.rdbuf();
  return p == q ? p = buf, q = p + i->sgetn(p, BufferSize), p == q ? EOF : *p++ : *p++;
}
inline void pc(char c) {
  static std::streambuf *o = std::cout.rdbuf();
  o->sputc(c);
}
struct Reader {
  template <class T>
  Reader &operator>>(T &w) {
    char c, p = 0;
    for (; !std::isdigit(c = gc());) if (c == '-') p = 1;
    for (w = c & 15; std::isdigit(c = gc()); w = w * 10 + (c & 15)) ;
    if (p) w = -w;
    return *this;
  }
} cin;
struct Putter {
  template <class T>
  Putter &operator<<(T w) {
    if (w == 0) return pc('0'), *this;
    if (w < 0) pc('-'), w = -w;
    static char s[20], *t;
    for (t = s; w; w /= 10) *++t = w % 10 + '0';
    while (t != s) pc(*t--);
    return *this;
  }
  Putter &operator<<(const char *s) {
    while (*s) pc(*s++);
    return *this;
  }
} cout;


constexpr int T = 1024, N(5e5 + 5);
struct Qry {
  int l, r, id;
  bool operator<(const Qry &rhs) const {
    return l / T == rhs.l / T ? l / T & 1 ? r < rhs.r : r > rhs.r : l < rhs.l;
  }
} q[N];
int a[N], c[N], d[N], t1[N], t2[N];
LL pre[N], pre1[N], ans[N], out[N];
std::array<int, 4> g[N << 1];
std::vector<int> fac[N];
int main() {
  std::ios::sync_with_stdio(false);
  std::cin.tie(nullptr);
  int n, m, max = 0;
  cin >> n >> m;
  for (int i = 0; i < n; i++) cin >> a[i], smax(max, a[i]);
  for (int i = 0; i < m; i++) {
    cin >> q[i].l >> q[i].r;
    q[i].l--, q[i].r--;
    q[i].id = i;
  }
  LL min = 1e18, sum = 0;
  int t = 1;
  for (int i = 0; i < n; i++) c[a[i]]++;
  for (int i = max; i; i--) {
    if (smin(min, sum + 1LL * i * n * 3)) t = i;
    sum += (LL)max / i * c[i];
  }
  for (int i = t + 1; i <= max; i++) {
    for (int j = i; j <= max; j += i) {
      fac[j].push_back(i);
    }
  }
  for (int i = 1; i <= t; i++) {
    memset(t1, 0, n << 2);
    memset(t2, 0, n << 2);
    for (int j = 0; j < n; j++) {
      t1[j] = a[j] % i == 0;
      t2[j] = a[j] == i;
    }
    for (int j = 1; j < n; j++) {
      t1[j] += t1[j - 1];
      t2[j] += t2[j - 1];
    }
    for (int j = 0; j < m; j++) {
      auto [l, r, id] = q[j];
      out[id] += 1LL * (t1[r] - (l ? t1[l - 1] : 0) - 1) * (t2[r] - (l ? t2[l - 1] : 0));
    }
  }
  for (int i = 0; i < n; i++) if (a[i] <= t) a[i] = 0;
  auto clr = [&]() {
    memset(c, 0, max + 1 << 2);
  };
  auto add = [&](int x) {
    if (!x) return;
    for (auto i : fac[x]) c[i]++;
    for (int i = x; i <= max; i += x) c[i]++;
  };
  auto ask = [&](int x) {
    return c[x];
  };
  clr();
  for (int i = 0; i < n - 1; i++) {
    add(a[i]);
    pre[i + 1] = ask(a[i]) + pre[i];
    pre1[i + 1] = ask(a[i + 1]) + pre1[i];
  }
  std::sort(q, q + m);
  int x = 1, y = 0, cnt = 0;
  for (int i = 0; i < m; i++) {
    if (x < q[i].l) {
      g[cnt++] = {y, x, q[i].l - 1, -i - 1};
    }
    if (x > q[i].l) {
      g[cnt++] = {y, q[i].l, x - 1, i};
    }
    ans[i] += pre[q[i].l] - pre[x];
    x = q[i].l;
    if (x) {
      if (y < q[i].r) {
    g[cnt++] = {x - 1, y + 1, q[i].r, -i - 1};
      }
      if (y > q[i].r) {
    g[cnt++] = {x - 1, q[i].r + 1, y, i};
      }
    }
    ans[i] += pre1[q[i].r] - pre1[y];
    y = q[i].r;
  }
  std::sort(g, g + cnt);
  clr();
  for (int i = 0, p = 0; i < n && p < cnt; i++) {
    add(a[i]);
    for (; p < cnt && g[p][0] == i; p++) {
      auto [k, l, r, id] = g[p];
      LL t = 0;
      for (int j = l; j <= r; j++) t += ask(a[j]);
      if (id < 0) {
    ans[-id - 1] -= t;
      } else {
    ans[id] += t;
      }
    }
  }
  for (int i = 1; i < m; i++) ans[i] += ans[i - 1];
  for (int i = 0; i < m; i++) out[q[i].id] += ans[i] + q[i].r - q[i].l + 1;
  for (int i = 0; i < m; i++) std::cout << out[i] << "\n";
  return 0;
}

P5399 [Ynoi2018] 駄作

给定 \(n\) 个节点的树,边权为 1。\(m\) 次询问,给定 \(p_0, d_0, p_1, d_1\),计算 \[ \sum_{d(p_0, a) \leq d_0}\sum_{d(p_1, b) \leq d_1} d(a, b) \]

\(1 \leq n, m \leq 10^5\)

Top cluster 树分块。

这个题前前后后写了几天,还是常数太大过不了。

每个邻域可以被分成每个块的邻域,只有 \(p_0, p_1\) 所在块的起点不是界点,其余都是。

不同块邻域之间的贡献,可以在收缩树上DP,这里需要预处理每个块以界点为起点的邻域的各种信息。

相同块邻域之间的贡献,如果起点不是界点,这样的情况只有 \(O(m)\) 次,在块内 DP,复杂度 \(O(m \sqrt n)\)

如果起点是界点,将询问离线,对于每个块,处理以界点为起点的邻域之间的答案。可以转换为求 \(\sum d(LCA)\),然后搞 \(O(B)\) 修改,\(O(1)\) 查询。

我的实现用到了预处理块与块的距离,点到界点的距离,块内 \(O(1)\) rmq lca 的方式来搞,常数太大了。

怎么卡常:通过阅读其他人的代码,可以发现,对于每一块,按照bfs序重新标号,这样每次寻找邻域时用bfs就比较 cache 友好,然后对于收缩树,我的代码中是直接以界点构成的树来DP的,但是好像别人是用块构成的树来DP的,所以我还要处理 rake 块的小情况,十分麻烦。

// Author:  HolyK
// Created: Mon Mar 28 17:33:17 2022
#include <bits/stdc++.h>

template <class T, class U>
inline bool smin(T &x, const U &y) {
  return y < x ? x = y, 1 : 0;
}
template <class T, class U>
inline bool smax(T &x, const U &y) {
  return x < y ? x = y, 1 : 0;
}

inline char gc() {
  static constexpr int BufferSize = 1 << 22 | 5;
  static char buf[BufferSize], *p, *q;
  static std::streambuf *i = std::cin.rdbuf();
  return p == q ? p = buf, q = p + i->sgetn(p, BufferSize), p == q ? EOF : *p++ : *p++;
}
struct Reader {
  template <class T>
  Reader &operator>>(T &w) {
    char c, p = 0;
    for (; !std::isdigit(c = gc());) if (c == '-') p = 1;
    for (w = c & 15; std::isdigit(c = gc()); w = w * 10 + (c & 15)) ;
    if (p) w = -w;
    return *this;
  }
} cin;

using LL = long long;
using PII = std::pair<int, int>;

std::mt19937_64 rng((std::random_device())());
LL rnd(LL l, LL r) {
  if (l > r) std::swap(l, r);
  return std::uniform_int_distribution<LL>(l, r)(rng);
}

constexpr int N(1e5 + 5), T(666), K(N / T + 5), S(N / T * 6 + 5);
std::vector<int> g[N];
int n, m, root, fa[N], dep[N], siz[N], bot[N];

std::vector<int> g1[N], pt[N];
int key[N], keys[N], fa1[N];
int gl[N], gr[N], bel[N], tot, end[N][2];
int in[N], st[10][N], lg[N];

void dfs(int x) {
  int cnt = 0;
  
  static int s[N], t; 
  for (int y : g[x]) {
    fa[y] = x;
    g[y].erase(std::find(g[y].begin(), g[y].end(), x));
    s[t++] = y;
    dep[y] = dep[x] + 1;
    dfs(y);
    siz[x] += siz[y];
    if (bot[y]) {
      bot[x] = bot[y];
      cnt++;
    }
  }

  if (x == root || siz[x] + 1 >= T || cnt > 1) {
    key[x] = ++keys[0];
    keys[keys[0]] = x;
    bot[x] = x;
    siz[x] = 0;

    int r = g[x].size(), c = 0, now = 0;
    for (int i = g[x].size() - 1; i >= 0; i--) {
      int y = g[x][i];
      if (c && bot[y] || now + siz[y] >= T) {
        int tt = t;
        do {
          bel[s[--t]] = tot;
          in[s[t]] = in[0]--;
        } while (s[t] != g[x][i + 1]);
        pt[tot].assign(s + t, s + tt);
        gl[tot] = i + 1, gr[tot] = r;
        r = i + 1;
        end[tot][0] = x;
        end[tot][1] = c;
        tot++;

        now = c = 0;
      }

      now += siz[y];
      if (bot[y]) {
        c = y = bot[y];
        g1[x].push_back(y);
        g1[y].push_back(x);
        fa1[y] = x;
      }
    }

    int tt = t;
    do {
      bel[s[--t]] = tot;
      in[s[t]] = in[0]--;
      
    } while (s[t] != g[x][0]);
    pt[tot].assign(s + t, s + tt);
    gl[tot] = 0, gr[tot] = r;
    end[tot][0] = x;
    end[tot][1] = c;
    tot++;
  }

  siz[x]++;
}

int get(int x, int y) {
  return in[x] < in[y] ? x : y;
}
int lca(int x, int y) {
  if (x == y) return x;
  x = in[x], y = in[y];
  if (x > y) std::swap(x, y);
  int k = lg[y - x];
  return get(st[k][x], st[k][y - (1 << k)]);
}
int dis(int x, int y) {
  return dep[x] + dep[y] - dep[lca(x, y)] * 2;
}
void initLCA() {
  for (int i = 1; i <= n; i++) {
    st[0][in[i] - 1] = fa[i];
  }
  
  for (int i = 2; i <= n; i++) {
    lg[i] = lg[i >> 1] + 1;
  }
  for (int i = 1; i < 10; i++) {
    for (int j = 1; j + (1 << i) - 1 <= n; j++) {
      st[i][j] = get(st[i - 1][j], st[i - 1][j + (1 << i - 1)]);
    }
  }
}

int vis[N], cnt[S][T][2], d2k[N][2], dk[K][K];
LL sum[S][T][2][2];

std::vector<PII> pts[S][2];
void init() {
  static PII q[N];
  int l, r;
  for (int i = 1; i <= n; i++) {
    if (!key[i]) continue;
    int o = key[i];
    q[l = r = 1] = {i, 0};
    vis[i] = ++vis[0];    
    while (l <= r) {
      auto [x, z] = q[l++];
      dk[o][key[x]] = z;
      for (int y : g1[x]) {
        if (vis[y] == vis[0]) continue;
        vis[y] = vis[0];
        q[++r] = {y, z + std::abs(dep[x] - dep[y])};
      }
    }
  }

  for (int i = 0; i < tot; i++) {
    int p = end[i][0], p1 = end[i][1];
    static int c[2][T];
    memset(c, 0, sizeof c);
    for (int x : pt[i]) {
      int dx = d2k[x][0] = dep[x] - dep[p];
      c[0][d2k[x][0]]++;
      cnt[i][dx][0]++;
      sum[i][dx][0][0] += dx;
      if (p1) {
        int dy = d2k[x][1] = dis(x, p1);
        c[1][d2k[x][1]]++;
        cnt[i][dy][1]++;
        sum[i][dx][0][1] += dy;
        sum[i][dy][1][0] += dx;
        sum[i][dy][1][1] += dy;
      }
    }
    
    pts[i][0].resize(pt[i].size());
    if (p1) pts[i][1].resize(pt[i].size());
    for (int j = 1; j < T; j++) c[0][j] += c[0][j - 1], c[1][j] += c[1][j - 1];
    for (int u = pt[i].size() - 1; u >= 0; u--){
      int x = pt[i][u];
      pts[i][0][--c[0][d2k[x][0]]] = {x, d2k[x][0]};
      if (p1) pts[i][1][--c[1][d2k[x][1]]] = {x, d2k[x][1]};
    }

    for (int j = 1; j < T; j++) {
      for (int k : {0, 1}) {
        cnt[i][j][k] += cnt[i][j - 1][k];
        sum[i][j][k][0] += sum[i][j - 1][k][0];
        sum[i][j][k][1] += sum[i][j - 1][k][1];        
      }
    }
  }
}

std::array<int, 4> qry[N];


struct Info {
  int c;
  LL s[2];
  void clear() {
    c = 0;
    s[0] = s[1] = 0;
  }
} rake[N][2], comp[N][2];

LL ans[N];
void dp(int x, int id) {
  ans[id] += rake[x][0].c * rake[x][1].s[0] + rake[x][1].c * rake[x][0].s[0];
  
  for (int y : g1[x]) {
    if (y == fa1[x]) continue;
    dp(y, id);

    // compress
    for (int i = 0; i < 2; i++) {
      ans[id] += rake[y][i].c * comp[y][!i].s[1] + rake[y][i].s[0] * comp[y][!i].c;
    } 
    for (int i = 0; i < 2; i++) {
      comp[y][i].c += rake[y][i].c;
      comp[y][i].s[0] += 1LL * rake[y][i].c * (dep[y] - dep[x]) + rake[y][i].s[0];
    }
    
    // rake
    for (int i = 0; i < 2; i++) {
      ans[id] += rake[x][i].c * comp[y][!i].s[0] + rake[x][i].s[0] * comp[y][!i].c;

    } 
    for (int i = 0; i < 2; i++) {
      rake[x][i].c += comp[y][i].c;
      rake[x][i].s[0] += comp[y][i].s[0];
    }
  }
}

void split(int x, int d, int k) {
  if (x == root) {
    rake[root][k].c++;
    for (int i = 0; i < tot; i++) {
      auto &f = end[i][1] ? comp[end[i][1]][k] : rake[end[i][0]][k];
      
      int q = dep[end[i][0]];
      if (q > d) continue;
      q = std::min(T - 1, d - q);
      f.c += cnt[i][q][0];
      f.s[0] += sum[i][q][0][0];
      f.s[1] += sum[i][q][0][1];
    }
    return;
  }
  
  int o = bel[x], ou = key[end[o][0]], od = key[end[o][1]];
  if (dep[x] <= d) rake[root][k].c++;
  for (int i = 0; i < tot; i++) {
    auto &f = end[i][1] ? comp[end[i][1]][k] : rake[end[i][0]][k];
    if (i == o) {
      if (key[x] || d >= T - 1) {
        int r = key[x] ? 1 : 0, q = std::min(d, T - 1);
        f.c += cnt[i][q][r];
        f.s[0] += sum[i][q][r][0];
        f.s[1] += sum[i][q][r][1];
      } else {
        for (auto v : pt[i]) {
          if (dis(x, v) > d) continue;
          f.c++;
          f.s[0] += d2k[v][0];
          f.s[1] += d2k[v][1];
        }
      }
      continue;
    }

    int iu = key[end[i][0]], id = key[end[i][1]];
    int r = 0, q = dk[ou][iu] + d2k[x][0];
    if (od) smin(q, dk[od][iu] + d2k[x][1]);
    if (id) {
      int qq = dk[ou][id] + d2k[x][0];
      if (od) smin(qq, dk[od][id] + d2k[x][1]);
      if (smin(q, qq)) r = 1;
    }

    if (q > d) continue;
    q = std::min(T - 1, d - q);

    f.c += cnt[i][q][r];
    f.s[0] += sum[i][q][r][0];
    f.s[1] += sum[i][q][r][1];
  }
}


LL val[N], s[N], ss, cc;

void getS(int x) {
  if (key[x]) return;
  for (int y : g[x]) {
    if (y == fa[x]) continue;
    s[y] = s[x] + val[y];
    getS(y);
  }
}
void update(int x, int y) {
  while (!key[fa[x]]) {
    val[x] += y;
    x = fa[x];
  }
  s[x] = val[x] += y;
  getS(x);  
  // std::cerr << "update " << x << "\n";
}

struct Node {
  int d, id;
  PII a2;
};

void cal(int o) {
  int p = end[o][0], p1 = end[o][1], ou = key[p], od = key[p1];

  int coef = p1 ? 1 : 0;

  std::vector<Node> b[2];
  for (int i = 1; i <= m; i++) {
    PII a[2];
    for (int k : {0, 1}) {
      int x = qry[i][k * 2], d = qry[i][k * 2 + 1];
      if (x == root) {
        if (d <= dep[p]) goto ed;
        a[k] = {0, d - dep[p]};
      } else if (bel[x] == o) {
        if (d >= T - 1 || key[x]) {
          a[k] = {key[x] ? 1 : 0, d};
        } else {
          a[k] = {-x, d};
        }
      } else {
        int r = 0, i = bel[x], iu = key[end[i][0]], id = key[end[i][1]], q = dk[ou][iu] + d2k[x][0];
        if (id) smin(q, dk[ou][id] + d2k[x][1]);
        if (od) {
          int qq = dk[od][iu] + d2k[x][0];
          if (id) smin(qq, dk[od][id] + d2k[x][1]);
          if (smin(q, qq)) r = 1;
        }
        if (q > d || q == d && !r) goto ed;
        a[k] = {r, d - q};
      }

      if (a[k].first >= 0) {
        smin(a[k].second, pts[o][a[k].first].back().second);
      }
    }
    
    if (a[0].first < 0) {
      std::swap(a[0], a[1]);
    }
    
    if (a[0].first < 0) {
      // std::cerr << "AA\n";
      int x1 = -a[0].first, d1 = a[0].second, x2 = -a[1].first, d2 = a[1].second;
      static int c[N][2];

      c[p][0] = c[p][1] = 0;
      for (int x : pt[o]) {
        c[x][0] = dis(x, x1) <= d1;
        c[x][1] = dis(x, x2) <= d2;
      }
      if (p1) {
        static LL s[N][2];
        s[p][0] = s[p][1] = 0;
        for (int x : pt[o]) s[x][0] = s[x][1] = 0;
        for (int j = pts[o][0].size() - 1; j >= 0; j--) {
          int x = pts[o][0][j].first, y = fa[x];
          s[x][0] += c[x][0], s[x][1] += c[x][1];
          ans[i] += c[x][0] * s[y][1] + s[x][0] * c[y][1] +
                    c[x][1] * s[y][0] + s[x][1] * c[y][0];

          c[y][0] += c[x][0], c[y][1] += c[x][1];
          s[y][0] += s[x][0], s[y][1] += s[x][1];
        }
      } else {
        for (int j = pts[o][0].size() - 1; j >= 0; j--) {
          int x = pts[o][0][j].first, y = fa[x];
          ans[i] -= 2LL * c[x][0] * c[x][1];
          c[y][0] += c[x][0], c[y][1] += c[x][1];
        }
      }
      
    } else {
      b[a[0].first].push_back({a[0].second, i, a[1]});
    }
    
    ed:;
  }

  for (int z : {0, 1}) {
    if (b[z].empty()) continue;
    int m = 0;
    for (auto &v : b[z]) smax(m, v.d);
    std::vector<int> c(m + 1);
    for (auto &v : b[z]) c[v.d]++;
    for (int i = 1; i <= m; i++) c[i] += c[i - 1];
    std::vector<Node> bb(b[z].size());
    for (auto &v : b[z]) bb[--c[v.d]] = v;

    for (int i = 0, j, k = 0; i < bb.size(); i = j) {
      int d = bb[i].d, m[2] = {};
      static LL sum[2][T];

      for (j = i; j < bb.size() && d == bb[j].d; j++) {
        if (bb[j].a2.first >= 0) smax(m[bb[j].a2.first], bb[j].a2.second);
      }
      while (k < pts[o][z].size() && pts[o][z][k].second <= d) {
        int x = pts[o][z][k++].first;
        update(x, 1);
        ss += d2k[x][0];
        cc += 1;
      }
      for (int i = 0; i <= m[0]; i++) sum[0][i] = 0;
      for (int i = 0; i <= m[1]; i++) sum[1][i] = 0;
      for (auto [x, d] : pts[o][0]) {
        if (d > m[0]) break;
        sum[0][d] += coef * (ss + 1LL * cc * d) - s[x] * 2;
      }
      for (int i = 1; i <= m[0]; i++) sum[0][i] += sum[0][i - 1];
      for (auto [x, d] : pts[o][1]) {
        if (d > m[1]) break;
        sum[1][d] += coef * (ss + 1LL * cc * d2k[x][0]) - s[x] * 2;
      }
      for (int i = 1; i <= m[1]; i++) sum[1][i] += sum[1][i - 1];

      for (int k = i; k < j; k++) {
        auto [x, d] = bb[k].a2;
        if (x >= 0) {
          // assert(d <= m[x]);
          ans[bb[k].id] += sum[x][d];
        } else {
          for (int v : pt[o]) {
            if (dis(v, -x) <= d) {
              ans[bb[k].id] += coef * (ss + 1LL * cc * d2k[v][0]) - s[v] * 2;
            }
          }
        }
      }
    }
    for (int x : pt[o]) s[x] = val[x] = 0, ss = cc = 0;
  }
}

void solve() {
  cin >> n;
  for (int i = 2, x; i <= n; i++) {
    cin >> x;
    g[x].push_back(i);
    g[i].push_back(x);
  }

  root = rnd(1, n);
  bel[root] = -1;
  in[0] = n, in[root] = 1;
  dfs(root);

  // for (int i = 1; i <= n; i++) {
  //   std::cerr << i << " " << bel[i] << "\n";
  // }
  initLCA();

  // std::cerr << "??\n";
  
  for (int i = 1; i <= n; i++) {
    if (i != root)
      g[i].push_back(fa[i]);
  }

  init();

  std::cerr << (double)clock() / CLOCKS_PER_SEC << "\n";
  cin >> m;
  for (int i = 1; i <= m; i++) {
    int x1, d1, x2, d2;
    cin >> x1 >> d1 >> x2 >> d2;
    qry[i] = {x1, d1, x2, d2};
    for (int j = 1; j <= keys[0]; j++) {
      int x = keys[j];
      rake[x][0].clear();
      rake[x][1].clear();
      comp[x][0].clear();
      comp[x][1].clear();
    }

    split(x1, d1, 0), split(x2, d2, 1);
    dp(root, i);
  }

  std::cerr << (double)clock() / CLOCKS_PER_SEC << "\n";
  for (int i = 0; i < tot; i++) {
    cal(i);
  }
  
  for (int i = 1; i <= m; i++) {
    std::cout << ans[i] << "\n";
  }
  
  std::cerr << (double)clock() / CLOCKS_PER_SEC << "\n";
}
int main() {
  // freopen("t.in", "r", stdin);
  // freopen("t1.out", "w", stdout);

  std::ios::sync_with_stdio(false);
  std::cin.tie(nullptr);
  
  int t = 1;
  
  // std::cin >> t;
  
  while (t--) {
    solve();
  }
  return 0;
}

P4119 [Ynoi2018] 未来日记

给定序列 \(a_n\)\(m\) 次操作,区间 \([l, r]\) 所有 \(x\) 改成 \(y\);查区间 \([l, r]\) 的第 \(k\) 小值。

\(1 \leq n, m, a_i \leq 10^5\)

序列分块+值域分块。

每个值、每个值域块维护序列块中出现次数的前缀和。

用并查集维护每个序列快中每个值的位置集合。

修改时重新计算对应的 \(O(1)\) 个值和值域块的前缀和。

查询时两边散块重新统计值和值域块的出现次数,中间整块直接查表。

然后先确定答案所在的值域块,再确定具体的值。

复杂度 \(O(n \sqrt n)\)

// Author:  HolyK
// Created: Fri Sep 17 08:24:37 2021
#include <bits/stdc++.h>
template <class T, class U>
inline bool smin(T &x, const U &y) {
  return y < x ? x = y, 1 : 0;
}
template <class T, class U>
inline bool smax(T &x, const U &y) {
  return x < y ? x = y, 1 : 0;
}

using LL = long long;
using PII = std::pair<int, int>;

constexpr int N(1e5 + 5), T(512), S(N / T + 1);
int n, m, tot, a[N], fa[N], rt[S][N];
int b[N][S], c[S][S]; // Inter-block(single, block) prefix

int find(int x) {
  while (x != fa[x]) x = fa[x] = fa[fa[x]];
  return x;
}

int main() {
  // freopen("t.in", "r", stdin);
  // freopen("t.out", "w", stdout);
  std::ios::sync_with_stdio(false);
  std::cin.tie(nullptr);
  std::cin >> n >> m;
  tot = (n - 1 + T) / T;
  
  memset(rt, -1, sizeof rt);
  int max = 0;
  for (int i = 0; i < n; i++) {
    std::cin >> a[i];
    rt[i / T][a[i]] = i;
    b[a[i]][i / T]++;
    c[a[i] / T][i / T]++;
    smax(max, a[i]);
  }
  for (int i = 1; i <= max; i++) {
    for (int j = 1; j < tot; j++) {
      b[i][j] += b[i][j - 1];
    }
  }
  for (int i = 0; i <= max / T; i++) {
    for (int j = 1; j < tot; j++) {
      c[i][j] += c[i][j - 1];
    }
  }
  for (int i = 0; i < n; i++) {
    fa[i] = rt[i / T][a[i]];
  }

  while (m--) {
    int opt, l, r, x, y;
    std::cin >> opt >> l >> r >> x;
    l--;
    
    if (opt == 1) {
      std::cin >> y;
      if (x == y) continue;
      static int tmp[S];
      for (int o = l / T, i = o * T, j; i < r; o++, i += T) {
        j = std::min(n, T + i);  
        int &u = rt[o][x], &v = rt[o][y];
        if (u == -1) continue;
        if (i < l || r < j) {
          for (int k = i; k < j; k++) {
            a[k] = a[find(k)];
          }
          u = v = -1;

          for (int k = std::max(l, i); k < j && k < r; k++) {
            if (a[k] == x) a[k] = y, tmp[o]++;
          }
          for (int k = i; k < j; k++) {
            if (a[k] == x) {
              u = k;
            } else if (a[k] == y) {
              v = k;
            }
          }

          for (int k = i; k < j; k++) {
            fa[k] = rt[o][a[k]];
          }
          
        } else {
          tmp[o] = b[x][o] - (o ? b[x][o - 1] : 0);
          if (~v) {
            fa[u] = v;
          } else {
            a[v = u] = y;
          }
          u = -1;
        }        
      }

      for (int i = l / T; i < tot; i++) {
        if (i) tmp[i] += tmp[i - 1];
        b[x][i] -= tmp[i];
        b[y][i] += tmp[i];
        c[x / T][i] -= tmp[i];
        c[y / T][i] += tmp[i];
      }
      for (int i = l / T; i < tot; i++) {
        tmp[i] = 0;
      }
    } else {
      static int tmp[N], blo[S];
      if (r - l <= T * 2) {
        for (int i = l; i < r; i++) tmp[i] = a[find(i)];
        // for (int i = l; i < r; i++) std::cerr << tmp[i] << " \n"[i + 1 == r];
        std::nth_element(tmp + l, tmp + l + x - 1, tmp + r);
        std::cout << tmp[l + x - 1] << "\n";
        for (int i = l; i < r; i++) tmp[i] = 0;
        continue;
      }
      r--;
      int bl = l / T, br = r / T;
      assert(bl + 1 < br);
      
      for (int i = l; i < bl * T + T; i++) {
        a[i] = a[find(i)];
        tmp[a[i]]++;
        blo[a[i] / T]++;
      }
      for (int i = br * T; i <= r; i++) {
        a[i] = a[find(i)];
        tmp[a[i]]++;
        blo[a[i] / T]++;
      }
      int d = 0;
      auto calSig = [&](int i) {
        return tmp[i] + b[i][br - 1] - b[i][bl];
      };
      auto calBlo = [&](int i) {
        return blo[i] + c[i][br - 1] - c[i][bl];
      };
      int z;
      while (x > (z = calBlo(d))) {
        x -= z, d++;
      }
      d *= T;
      while (x > (z = calSig(d))) {
        x -= z, d++;
      }
      std::cout << d << "\n";

      for (int i = l; i < bl * T + T; i++) {
        tmp[a[i]] = 0;
        blo[a[i] / T] = 0;
      }
      for (int i = br * T; i <= r; i++) {
        tmp[a[i]] = 0;
        blo[a[i] / T] = 0;
      }
    }
  }
  return 0;
}

P4117 [Ynoi2018] 五彩斑斓的世界

给定序列 \(a_n\)\(m\) 次操作,区间大于 \(x\) 的减 \(x\);区间查询 \(x\) 的出现次数。

\(1 \leq n \leq 10^6, 1 \leq m \leq 5 \times 10^5, 0 \leq a, x \leq 10^5 + 1\)

空间限制 64MB。

序列分块,每个块对答案的贡献是独立的,所以可以考虑将询问离线,每个块单独处理贡献,这样空间就比较小。

用并查集维护每个值出现的位置集合,然后考虑当前块的最大值 \(max\)

如果 \(x > max / 2\),那么操作之后最大值会变成 \(< x\),如果 \(x \leq max / 2\),那么操作之后最大值会减少 \(x\)

即操作一次,最大值至少会减少 \(\min\{max - x, x\}\)

  • 如果 \(\min\{max-x, x\} \leq \sqrt m\),我们用 \(O(\min\{max-x, x\})\) 的时间来搞这个操作就行。
    • 如果 \(max - x < x\),那么直接枚举 \(x + 1\cdots max\) 并查集合并就行。
    • 如果 \(max - x > x\),可以先把 \(1 \cdots x\) 这些数加 \(x\),然后再整体打标记减 \(x\)
    这样上面的操作不会超过 \(m\) 次,复杂度 \(O(m\sqrt n)\)
  • 如果 \(\min\{max-x, x\} > \sqrt m\),那么最多 \(\sqrt m\) 次就会把最大值减到 1,所以每次暴力减就行,复杂度 \(O(n\sqrt m)\)

总复杂度 \(O(n\sqrt m + m\sqrt n)\)

// Author:  HolyK
// Created: Fri Sep 10 15:54:37 2021
#include <bits/stdc++.h>
template <class T, class U>
inline bool smin(T &x, const U &y) {
  return y < x ? x = y, 1 : 0;
}
template <class T, class U>
inline bool smax(T &x, const U &y) {
  return x < y ? x = y, 1 : 0;
}

using LL = long long;
using PII = std::pair<int, int>;

inline char gc() {
  static constexpr int BufferSize = 1 << 22 | 5;
  static char buf[BufferSize], *p, *q;
  static std::streambuf *i = std::cin.rdbuf();
  return p == q ? p = buf, q = p + i->sgetn(p, BufferSize), p == q ? EOF : *p++ : *p++;
}
struct Reader {
  template <class T>
  Reader &operator>>(T &w) {
    char c, p = 0;
    for (; !std::isdigit(c = gc());) if (c == '-') p = 1;
    for (w = c & 15; std::isdigit(c = gc()); w = w * 10 + (c & 15)) ;
    if (p) w = -w;
    return *this;
  }
} cin;

constexpr int N(1e6 + 5), T(888), M(2e5 + 5);
struct Option {
  int opt, l, r, x;
} q[N];

int n, m, a[N], ans[N];
int pos[M];
int fa[T], siz[T];
int find(int x) {
  while (x != fa[x]) x = fa[x] = fa[fa[x]];
  return x;
}
int main() {
  // freopen("t.in", "r", stdin);
  // freopen("t.out", "w", stdout);
  std::ios::sync_with_stdio(false);
  std::cin.tie(nullptr);
  cin >> n >> m;
  for (int i = 0; i < n; i++) cin >> a[i];
  for (int i = 1; i <= m; i++) {
    cin >> q[i].opt >> q[i].l >> q[i].r >> q[i].x;
    q[i].l--;
  }
  memset(pos, -1, sizeof pos);
  for (int x = 0; x < n; x += T) {
    int y = std::min(n, x + T), max, tag;
    
    auto build = [&] {
      max = tag = 0;
      memset(siz, 0, sizeof siz);
      for (int i = x; i < y; i++) {
        smax(max, a[i]);
        pos[a[i]] = i - x;
      }
      for (int i = x; i < y; i++) {
        fa[i - x] = pos[a[i]];
        siz[fa[i - x]]++;
      }
    };
    build();
    for (int i = 1; i <= m; i++) {
      if (q[i].l >= y || q[i].r <= x) continue;
      if (q[i].l <= x && y <= q[i].r) {
        if (q[i].opt == 2) {
          if (~pos[q[i].x + tag]) {
            ans[i] += siz[pos[q[i].x + tag]];
          }
          continue;
        }
        if (max <= q[i].x || !q[i].x) continue;
        if (std::min(max - q[i].x, q[i].x) <= T) {
          if (max <= q[i].x * 2) {
            for (int j = q[i].x + 1 + tag; j <= max + tag; j++) {
              if (pos[j] == -1) continue;
              int &u = pos[j], &v = pos[j - q[i].x];
              if (v == -1) {
                v = u, a[x + u] -= q[i].x;
              } else {
                fa[u] = v;
                siz[v] += siz[u];
                siz[u] = 0;
              }
              u = -1;
            }
            max = q[i].x;
          } else {
            for (int j = q[i].x + tag; j >= tag; j--) {
              if (pos[j] == -1) continue;
              int &u = pos[j], &v = pos[j + q[i].x];
              if (v == -1) {
                v = u, a[x + u] += q[i].x;
              } else {
                fa[u] = v;
                siz[v] += siz[u];
                siz[u] = 0;
              }
              u = -1;
            }
            tag += q[i].x;
            max -= q[i].x;
          }
          continue;
        }
      }
      if (q[i].opt == 1 && max <= q[i].x) continue;
      if (q[i].opt == 2 && pos[q[i].x + tag] == -1) continue;
      for (int j = x; j < y; j++) {
        a[j] = a[x + find(j - x)];
      }
      for (int j = x; j < y; j++) {
        pos[a[j]] = -1;
        a[j] -= tag;
      }
      int l = std::max(q[i].l, x), r = std::min(q[i].r, y);
      
      if (q[i].opt == 1) {   
        for (int j = l; j < r; j++) {
          if (a[j] > q[i].x) a[j] -= q[i].x;
        }
      } else {
        for (int j = l; j < r; j++) {
          if (a[j] == q[i].x) ans[i]++;
        }
      }
      build();
    }
    for (int i = x; i < y; i++) {
      if (fa[i - x] == i - x) pos[a[i]] = -1;
    }
  }
  for (int i = 1; i <= m; i++) {
    if (q[i].opt == 2) {
      std::cout << ans[i] << "\n";
    }
  }
  std::cerr << (double)clock() / CLOCKS_PER_SEC << "\n";
  return 0;
}

P8205 [Ynoi2005] vti

\(n\) 个点的有根树,有边权,\(m\) 次询问,每次给出 \(t_i\) 个点,求它们的虚树包含的边的祖孙顺序对。

\(1 \leq n \leq 10^5, 1 \leq m \leq 10^6, \sum t_i \leq 10^6\).

可以通过一些简单计算,将问题转换为 \(O(\sum t_i)\) 次祖孙链查询。

树分块之后,因为都是祖孙链查询,考虑二次离线莫队。

左端点在同一块的,按照右端点dfs序排序,然后和普通莫队差不多。

需要讨论左右端点是否在同一块里面,两个需要分开计算。

二次离线莫队最后要根据前缀和计算答案,因为最后忘记区分这俩,调了一会。

需要求树上前缀的答案,这个用树状数组就行。

还要 \(O(1)\) 查询某个前缀比某个点小的数的个数,经典问题,搞一个 \(O(\sqrt n)\) 修改,\(O(1)\) 查询的数据结构。

// Author:  HolyK
// Created: Sat Apr  2 20:50:00 2022
#include <bits/stdc++.h>

template <class T, class U>
inline bool smin(T &x, const U &y) {
  return y < x ? x = y, 1 : 0;
}
template <class T, class U>
inline bool smax(T &x, const U &y) {
  return x < y ? x = y, 1 : 0;
}

using LL = long long;
using PII = std::pair<int, int>;

constexpr int N(1e5 + 5), M(1e6 + 5);
int n, m;
std::vector<int> g[N];

int a[N], fa[N], in[N], out[N], dep[N], dfn;
namespace RMQLCA {
int st[17][N], lg[N];
int get(int x, int y) {
  return in[x] < in[y] ? x : y;
}

void init() {
  for (int i = 1; i <= n; i++) st[0][in[i] - 1] = fa[i];
  for (int i = 2; i <= n; i++) lg[i] = lg[i >> 1] + 1;
  for (int i = 1; i < 17; i++) {
    for (int j = 1; j + (1 << i) - 1 <= n; j++) {
      st[i][j] = get(st[i - 1][j], st[i - 1][j + (1 << i - 1)]);
    }
  }
}

int lca(int x, int y) {
  if (x == y) return x;
  x = in[x], y = in[y];
  if (x > y) std::swap(x, y);
  int k = lg[y - x];
  return get(st[k][x], st[k][y - (1 << k)]);
}
}

using RMQLCA::lca;

LL pre1[N], pre2[N];
int c[N];
void dfs(int x) {

  in[x] = ++dfn;
  for (int p = a[x]; p <= n; p += p & -p) c[p]++;
  for (int p = a[x] - 1; p; p -= p & -p) pre1[x] += c[p];
  pre2[x] += dep[x];
  for (int p = a[x]; p; p -= p & -p) pre2[x] -= c[p];

  for (int y : g[x]) {
    pre1[y] = pre1[x];
    pre2[y] = pre2[x];
    dep[y] = dep[x] + 1;
    dfs(y);
  }

  out[x] = dfn;
  for (int p = a[x]; p <= n; p += p & -p) c[p]--;
}


int siz[N], bot[N], bel[N], tot;
bool key[N];

struct Qry {
  int l, r, k, id;
  bool operator<(const Qry &rhs) const {
    return bel[l] == bel[rhs.l] ? in[r] < in[rhs.r] : bel[l] < bel[rhs.l];
  }
} q[M * 2];
int qcnt, B;
LL ans[M * 2], oans[M];

struct Block {
  int top, bot;
} b[N];

void build(int x) {
  static int q[N], r;
  auto add = [&](int d) {
    int o = tot++;
    b[o] = {x, d};
    for (int i = 1; i <= r; i++) {
      int u = q[i];
      bel[u] = o;
      if (key[u]) continue;
      for (int v : g[u]) q[++r] = v;
    }
  };

  int s = 0, c = 0;
  for (int y : g[x]) {
    build(y);
    s += siz[y];
    if (bot[y]) {
      c++;
      bot[x] = bot[y];
    }
  }

  if (1 + s >= B || c > 1 || !x) {
    std::sort(g[x].begin(), g[x].end(), [](int i, int j) {
      return siz[i] < siz[j];
    });

    siz[x] = 1;
    bot[x] = x;
    key[x] = true;
    s = c = r = 0;

    for (int y : g[x]) {
      if (c && bot[y] || siz[y] + s >= B) {
        add(c);
        s = c = r = 0;
      }

      c |= bot[y];
      s += siz[y];
      q[++r] = y;
    }

    add(c);
  } else {
    siz[x] = 1 + s;
  }
}

std::vector<Qry> q0[N], q1[N], q2[N];

constexpr int T(256);
struct SumDS {
  int tot, s0[N], s1[N / T + 1];
  void add(int p, int x) {
    int k = p / T + 1, r = std::min(k * T, n + 1);
    while (p < r) s0[p++] += x;
    while (k < tot) s1[k++] += x;
  }
  int ask(int p) {
    return s0[p] + s1[p / T];
  }
} sumDS;

void dfsq(int x) {

  sumDS.add(a[x], 1);
  for (auto &[l, r, k, id] : q0[x]) {
    LL s = 0;
    while (r != l) s += sumDS.ask(a[r] - 1), r = fa[r];
    oans[id] += s * k;
  }
  for (auto &[l, r, k, id] : q1[x]) {
    LL s = 0;
    while (r != l) s += sumDS.ask(a[r] - 1), r = fa[r];
    ans[id] += s * k;
  }
  for (auto &[l, r, k, id] : q2[x]) {
    LL s = 0;
    while (r != l) s += dep[x] - sumDS.ask(a[r]), r = fa[r];
    ans[id] += s * k;
  }
  
  for (int y : g[x]) {
    dfsq(y);
  }
  sumDS.add(a[x], -1);
}

void solve() {
  std::cin >> n;
  a[1] = n;
  for (int i = 2; i <= n; i++) {
    std::cin >> fa[i] >> a[i];
    g[fa[i]].push_back(i);
  }

  dep[1] = 1;
  dfs(1);
  
  RMQLCA::init();
  
  std::cin >> m;
  for (int i = 1; i <= m; i++) {
    int t;
    std::cin >> t;
    std::vector<int> p(t);
    for (int j = 0; j < t; j++) std::cin >> p[j];
    std::sort(p.begin(), p.end(), [](int i, int j) {
      return in[i] < in[j];
    });
    for (int j = 0; j + 1 < t; j++) {
      p.push_back(lca(p[j], p[j + 1]));
    }

    std::sort(p.begin(), p.end(), [](int i, int j) {
      return in[i] < in[j];
    });
    p.erase(std::unique(p.begin(), p.end()), p.end());

    t = p.size();
    std::vector<int> s = {0}, c(t);
    for (int j = 1, x, u; j < t; j++) {
      x = p[j];
      for (;;) {
        u = p[s.back()];
        if (in[u] <= in[x] && in[x] <= out[u]) break;
        s.pop_back();
      }
      c[s.back()]++;
      s.push_back(j);
    }

    for (int j = 1; j < t; j++) {
      q[++qcnt] = {p[0], p[j], 1 - c[j], i};
    }
  }

  B = n / sqrt(qcnt) * 1.5;
  
  g[0] = {1};
  build(0);
  
  std::sort(q + 1, q + 1 + qcnt);
  for (int i = 1, j; i <= qcnt; i = j) {
    int o = bel[q[i].l];
    for (j = i + 1; j <= qcnt && o == bel[q[j].l]; j++) ;

    for (int k = i, x = b[o].bot, y = b[o].bot; k < j; k++) {
      if (bel[q[k].r] == o) {
        q0[q[k].l].push_back({q[k].l, q[k].r, -q[k].k, q[k].id});
        oans[q[k].id] += q[k].k * (pre1[q[k].r] - pre1[q[k].l]);
      } else {
        int l = q[k].l, r = q[k].r, t = lca(y, r);
        if (t != y) {
          ans[k] -= pre1[y] - pre1[t];
          q1[x].push_back({t, y, 1, k});
          y = t;
        }
        if (y != r) {
          ans[k] += pre1[r] - pre1[y];
          q1[x].push_back({y, r, -1, k});
          y = r;
        }
        if (x != l) {
          if (in[x] < in[l]) {
            ans[k] += pre2[l] - pre2[x];
            q2[y].push_back({x, l, -1, k});
          } else {
            ans[k] -= pre2[x] - pre2[l];
            q2[y].push_back({l, x, 1, k});
          }
          x = l;
        }
      }
    }
  }

  sumDS.tot = n / T + 1;
  dfsq(1);

  for (int i = 1; i <= qcnt; i++) {

    if (i > 1 && bel[q[i].l] == bel[q[i - 1].l]) {
      ans[i] += ans[i - 1];
    }

    if (bel[q[i].l] != bel[q[i].r]) oans[q[i].id] += ans[i] * q[i].k;
  }

  for (int i = 1; i <= m; i++) {
    std::cout << oans[i] << "\n";
  }
}

int main() {
  // freopen("t.in", "r", stdin);
  
  std::ios::sync_with_stdio(false);
  std::cin.tie(nullptr);
  
  int t = 1;
  
  // std::cin >> t;
  
  while (t--) {
    solve();
  }
  return 0;
}

P6778 [Ynoi2009] rpdq

给定 \(n\) 个点的无根有边权的树,\(m\) 次询问,求 \[ \sum_{l \leq i < j \leq r} \operatorname{dis}(i, j) \]

\(1 \leq n, m, d \leq 2\times 10^5\)

答案对 \(2^{32}\) 取模。

二次离线莫队之后转换成求一个点到前缀 \(1\dots k\) 的距离之和。转换成求它们的 lca 的深度之和。变成经典的链加链和,可以用树分块做到 \(O(\sqrt n)\) 修改,\(O(1)\) 查询。

这里树分块比较简单,只需要求出关键点,使得每个点向上最多跳 \(\sqrt n\) 步就能到达某个关键点。

之前卡常卡魔怔了,这个代码貌似用bfs序重新标了一下号,感觉很毒瘤。

// Author:  HolyK
// Created: Mon Apr 11 10:09:33 2022
#include <bits/stdc++.h>

template <class T, class U>
inline bool smin(T &x, const U &y) {
  return y < x ? x = y, 1 : 0;
}
template <class T, class U>
inline bool smax(T &x, const U &y) {
  return x < y ? x = y, 1 : 0;
}

using LL = long long;
using PII = std::pair<int, int>;
inline char gc() {
  static constexpr int BufferSize = 1 << 22 | 5;
  static char buf[BufferSize], *p, *q;
  static std::streambuf *i = std::cin.rdbuf();
  return p == q ? p = buf, q = p + i->sgetn(p, BufferSize), p == q ? EOF : *p++ : *p++;
}
struct Reader {
  template <class T>
  Reader &operator>>(T &w) {
    char c, p = 0;
    for (; !std::isdigit(c = gc());) if (c == '-') p = 1;
    for (w = c & 15; std::isdigit(c = gc()); w = w * 10 + (c & 15)) ;
    if (p) w = -w;
    return *this;
  }
} cin;

constexpr int N(2e5 + 5), T(512), S(T * 4 + 5);
int n, m, B, siz[N], bot[N], fa[N], val[N], bup[N], bdn[N], kp[S];
int que[N], out[N], id[N], key[N], kid[S], kcnt;
unsigned dis[N], dep[N];
std::vector<PII> g[N];
void build(int x) {
  static int l = 1, r = 0;
  int c = 0;
  for (auto [y, z] : g[x]) {
    if (x) g[y].erase(std::find(g[y].begin(), g[y].end(), PII{x, z}));
    fa[y] = x;
    dep[y] = dep[x] + z;
    build(y);
    siz[x] += siz[y];
    if (bot[y]) {
      c++;
      bot[x] = bot[y];
    }
  }
  if (!x || siz[x] + 1 >= B || c > 1) {
    key[x] = ++kcnt;
    bot[x] = kcnt;
    siz[x] = 0;
    for (auto [y, z] : g[x]) {
      que[++r] = y;
      val[r] = z;
      if (bot[y]) {
        dis[r] = z;
        kp[bot[y]] = kcnt;
      }
      int u = r, bu = kcnt, bd = bot[y];
      for (; l <= r; l++) {
        int x = que[l];
        bup[l] = bu;
        bdn[l] = bd;
        if (key[x]) continue;
        for (auto [y, z] : g[x]) {
          que[++r] = y;
          val[r] = z;
          dis[r] = dis[l];
          if (bot[y]) dis[r] += z;
        }
      }
      out[u] = r;
    }
  }
  siz[x]++; 
}

void relabel() {
  static int tmp[N];
#define R(a) for (int i = 0; i <= n; i++) tmp[i] = a[que[i]]; memcpy(a, tmp, sizeof tmp)
  R(bot);
  R(fa);
  R(key);
  for (int i = 1; i <= n; i++) id[que[i]] = i;
  for (int i = 1; i <= n; i++) {
    fa[i] = id[fa[i]];
    if (key[i]) {
      kid[key[i]] = i;
    }
  }
}

unsigned v[N], s[N], ks[S], tag[S];
void add(int x) {
  while (!key[fa[x]]) {
    v[x] += val[x];
    x = fa[x];
  }
  assert(out[x]);
  s[x] = v[x] += val[x];
  for (int i = x + 1; i <= out[x]; i++) {
    s[i] = v[i] + s[fa[i]];
  }
  x = bup[x];
  while (x < kcnt) {
    tag[x]++;
    x = kp[x];
  }
  for (int i = kcnt - 1; i; i--) {
    ks[i] = ks[kp[i]] + dis[kid[i]] * tag[i] + s[kid[i]];
  }
}

unsigned ask(int x) {
  return key[x] ? ks[key[x]] : dis[x] * tag[bdn[x]] + s[x] + ks[bup[x]];
}

unsigned pre[N], ans[N], oans[N];
struct Qry {
  int l, r, id;
  bool operator<(const Qry &rhs) const {
    return l / T == rhs.l / T ? l / T & 1 ? r < rhs.r : r > rhs.r : l < rhs.l;
  }
} q[N];

struct Q {
  int l, r, k, id;
};
std::vector<Q> qq[N];
void solve() {
  cin >> n >> m;
  B = 1.5 * sqrt(n);
  for (int i = 1, x, y, z; i < n; i++) {
    cin >> x >> y >> z;
    g[x].push_back({y, z});
    g[y].push_back({x, z});
  }
  g[0].push_back({n + 1 >> 1, 0});
  build(0);
  relabel();
  unsigned sum = 0;
  for (int i = 1; i <= n; i++) {
    add(id[i]);
    sum += dep[i];
    pre[i] = pre[i - 1] + sum + i * dep[i] - 2 * ask(id[i]);
  }
  
  for (int i = 1; i <= m; i++) {
    int l, r;
    cin >> l >> r;
    q[i] = {l, r, i};
  }
  std::sort(q + 1, q + 1 + m);
  for (int i = 1, x = 1, y = 0; i <= m; i++) {
    if (y != q[i].r) {
      ans[i] += pre[q[i].r] - pre[y];
      if (y < q[i].r) {
        qq[x - 1].push_back({y + 1, q[i].r, -1, i});
      } else {
        qq[x - 1].push_back({q[i].r + 1, y, 1, i});
      }
      y = q[i].r;
    }
    if (x != q[i].l) {
      ans[i] += pre[q[i].l - 1] - pre[x - 1];
      if (x < q[i].l) {
        qq[y].push_back({x, q[i].l - 1, -1, i});
      } else {
        qq[y].push_back({q[i].l, x - 1, 1, i});
      }
      x = q[i].l;
    }
  }
  memset(v, 0, sizeof v);
  memset(s, 0, sizeof s);
  memset(ks, 0, sizeof ks);
  memset(tag, 0, sizeof tag);

  sum = 0;
  for (int i = 1; i <= n; i++) {
    add(id[i]);
    sum += dep[i];
    for (auto &[l, r, k, t] : qq[i]) {
      LL s = 0;
      for (int j = l; j <= r; j++) {
        s += sum + i * dep[j] - 2 * ask(id[j]);
      }
      ans[t] += k * s;
    }
  }
  for (int i = 1; i <= m; i++) {
    ans[i] += ans[i - 1];
    oans[q[i].id] = ans[i];
  }
  for (int i = 1; i <= m; i++) {
    std::cout << oans[i] << "\n";
  }
}

int main() {
  // freopen("t.in", "r", stdin);
  
  std::ios::sync_with_stdio(false);
  std::cin.tie(nullptr);
  
  int t = 1;
  
  // std::cin >> t;
  
  while (t--) {
    solve();
  }
  return 0;
}

P5064 [Ynoi2014] 等这场战争结束之后

给你一个图,每个点有点权,最开始没有边。

有一些操作:

  1. 添加一条 \(x\)\(y\) 之间的双向边。
  2. 回到第 \(x\) 次操作后的状态(注意这里的 \(x\) 可以是 0,即回到初始状态)。
  3. 查询 \(x\) 所在联通块能到的点中点权第 \(y\) 小的值,如果不存在,那么输出 -1。

\(1 \leq n, m \leq 10^5, 0 \leq a_i \leq 10^9\)

zx2003 的论文题,复杂度是 \(O(m\sqrt n)\) 时间,\(O(n)\) 空间,但是跑不过暴力。

先离散化,注意到这里的离散化不需要去重。

建出操作树,然后可以预处理每个操作涉及到的点在当时所属并查集的根(因为要回滚,所以并查集是按秩合并),这个是 \(O(m\log n)\) 的。

值域分块,对于每个询问,我们先求出它属于哪个值域块。

对于每个值域块,用 01 表示某个点是否属于这个值域块,然后用并查集维护每个联通块的和,就可以知道每个联通块里面 1 的个数。注意到并查集的合并是 \(O(1)\) 的,而查询在前面已经预处理过。

所以这步总共是 \(O(m \sqrt n)\)

然后要求出每个询问的具体答案,需要支持 \(O(1)\) 询问一个点是否和当前的点联通。

将操作树分块,只需要求出关键点。每个关键点处理整张图的连通性,每个操作距离关键点最多只会连 \(O(\sqrt n)\) 条边,将这些边用前向星存下来之后,每次操作bfs一下就可求出当前联通块有哪些点了。

我在实现的时候,为了方便,在关键点处改用了路径压缩并查集。

// Author:  HolyK
// Created: Tue Apr 12 00:19:05 2022
#include <bits/stdc++.h>

template <class T, class U>
inline bool smin(T &x, const U &y) {
  return y < x ? x = y, 1 : 0;
}
template <class T, class U>
inline bool smax(T &x, const U &y) {
  return x < y ? x = y, 1 : 0;
}

using LL = long long;
using PII = std::pair<int, int>;

constexpr int N(1e5 + 5), T(512);
int n, m, a[N], ch[N], g[N], fa[N];
PII b[N];
struct Option {
  int opt, x, y;
} q[N];
bool key[N];
int ans[N], siz[N], bot[N];
int f[N], s[N];
int find(int x) {
  while (x != f[x]) x = f[x];
  return x;
}
int find2(int x) {
  while (x != f[x]) x = f[x] = f[f[x]];
  return x;
}

int seq[N * 2], scnt;
void dfs1(int x) {
  if (q[x].opt == 1) {
    int &u = q[x].x, &v = q[x].y;
    u = find(u), v = find(v);
    if (u != v) {
      seq[scnt++] = x;
      if (s[u] > s[v]) std::swap(u, v);
      s[v] += s[u];
      f[u] = v;
    }
    
  } else if (q[x].opt == 3) {
    q[x].x = find(q[x].x);
    seq[scnt++] = x;
  }
  
  for (int i = ch[x], y; y = g[i], i < ch[x + 1]; i++) {
    dfs1(y);
  }
  if (q[x].opt == 1) {
    int u = q[x].x, v = q[x].y;
    if (u != v) {
      seq[scnt++] = x;
      s[v] -= s[u];
      f[u] = u;
    }
  }
}

int head[N], to[N], next[N], tot;
void add(int x, int y) {
  to[++tot] = y, next[tot] = head[x], head[x] = tot;
}
void del(int x, int y) {
  head[x] = next[tot--];
}

int tmp[N];
void dfs3(int x) {
  if (key[x]) return;
  if (q[x].opt == 1) {
    int u = q[x].x, v = q[x].y;
    if (u != v) {
      add(u, v), add(v, u);
    }
  } else if (q[x].opt == 3) {
    // std::cerr << "! dfs3 " << q[x].x << " " << q[x].y << "\n";
    static int que[N], r, vis[N], vcnt;
    que[r = 1] = q[x].x;
    vcnt++;
    for (int i = 1; i <= r; i++) {
      int x = que[i];
      vis[x] = vcnt;
      for (int j = head[x]; j; j = next[j]) {
        int y = to[j];
        if (vis[y] == vcnt) continue;
        que[++r] = y;
      } 
    }
    while (ans[x] < n) {
      if (vis[find2(b[ans[x]].second)] == vcnt && !q[x].y--) break; 
      ans[x]++;
    }
  }
  for (int i = ch[x], y; y = g[i], i < ch[x + 1]; i++) {
    dfs3(y);
  }
  if (q[x].opt == 1) {
    int u = q[x].x, v = q[x].y;
    if (u != v) {
      del(v, u), del(u, v);
    }
  }
}
void work(int x) {
  // std::cerr << "key " << x << "\n";
  memcpy(tmp, f, sizeof f);
  if (q[x].opt == 3) {
    while (ans[x] < n) {
      if (find2(b[ans[x]].second) == q[x].x && !q[x].y--) break; 
      ans[x]++;
    }
  }
  for (int i = ch[x], y; y = g[i], i < ch[x + 1]; i++) dfs3(y);
  memcpy(f, tmp, sizeof f);
}

void dfs2(int x) {
  if (q[x].opt == 1 && q[x].x != q[x].y) f[q[x].x] = q[x].y;
  int c = 0;
  for (int i = ch[x], y; y = g[i], i < ch[x + 1]; i++) {
    dfs2(y);
    siz[x] += siz[y];
    if (bot[y]) {
      c++;
      bot[x] = bot[y];
    }
  }
  if (!x || siz[x] + 1 >= T || c > 1) {
    key[x] = true;
    bot[x] = x;
    siz[x] = 0;
    work(x);
  }
  siz[x]++;
  if (q[x].opt == 1 && q[x].x != q[x].y) f[q[x].x] = q[x].x;
}

void solve() {
  std::cin >> n >> m;
  for (int i = 0; i < n; i++) {
    std::cin >> a[i];
    b[i] = {a[i], i};
  }
  std::sort(b, b + n);
  for (int i = 0; i < n; i++) {
    a[b[i].second] = i;
  }
  for (int i = 1; i <= m; i++) {
    int opt, x, y = 0, &f = fa[i];
    std::cin >> opt >> x;
    if (opt == 2) {
      f = x;
    } else {
      f = i - 1;
      std::cin >> y;
      x--, y--;
    }
    ch[f]++;
    q[i] = {opt, x, y};
  }
  for (int i = 1; i <= m; i++) ch[i] += ch[i - 1];
  for (int i = 1; i <= m; i++) g[--ch[fa[i]]] = i;
  for (int i = 0; i < n; i++) f[i] = i, s[i] = 1;
  dfs1(0);
  int tot = (n - 1) / T + 1;
  for (int i = 0; i < tot; i++) {
    for (int j = 0; j < n; j++) {
      s[j] = a[j] / T == i;
    }
    for (int j = 0; j < scnt; j++) {
      auto &[opt, x, y] = q[seq[j]];
      if (opt == 1) {
        if (f[x] == x) {
          f[x] = y;
          s[y] += s[x];
        } else {
          f[x] = x;
          s[y] -= s[x];
        }
      } else {
        if (ans[seq[j]] / T != i) continue;
        if (s[x] <= y) {
          y -= s[x];
          ans[seq[j]] += T;
        }
      }
    }
  }
  for (int i = 0; i < n; i++) {
    assert(f[i] == i);
  }
  dfs2(0);
  for (int i = 1; i <= m; i++) {
    if (q[i].opt == 3) {
      std::cout << (ans[i] >= n ? -1 : b[ans[i]].first) << "\n";
    }
  }
}

int main() {
  // freopen("t.in", "r", stdin);
  
  std::ios::sync_with_stdio(false);
  std::cin.tie(nullptr);
  
  int t = 1;
  
  // std::cin >> t;
  
  while (t--) {
    solve();
  }
  return 0;
}

P4690 [Ynoi2016] 镜中的昆虫

给定序列 \(a_n\)\(m\) 次操作。区间赋值,区间数颜色。

\(1 \leq n, m \leq 10^5, 1 \leq a_i \leq 10^9\)

空间 64MB。

数颜色的套路,数 \(pre < l, l \leq i \leq r\) 的个数,再加一维时间,是三维偏序问题。

用 set 维护区间同色段之后就变成单点修改了,用 CDQ 分治搞一下三维偏序就行,但是这毒瘤空间那么小,我好像搞了多次 CDQ 才卡过,但是后来一次也搞过了,忘了为啥。

时间复杂度 \(O(n\log^2 n)\),空间复杂度 \(O(n)\)

// Author:  HolyK
// Created: Tue Apr 12 15:02:43 2022
#include <bits/stdc++.h>

template <class T, class U>
inline bool smin(T &x, const U &y) {
  return y < x ? x = y, 1 : 0;
}
template <class T, class U>
inline bool smax(T &x, const U &y) {
  return x < y ? x = y, 1 : 0;
}

using LL = long long;
using PII = std::pair<int, int>;

constexpr int N(1e5 + 5), Q(N * 15);
int n, m, a[N], la, ans[N];
std::map<int, std::set<int>> g;
std::set<int> p;

struct Option {
  int x, l, r, id;
  bool operator<(const Option &rhs) const {
    return x < rhs.x || x == rhs.x && id < rhs.id;
  }
} s[Q], t[Q];
int cnt;

namespace Fenwick {
int c[N];
void add(int p, int x) {
  for (; p <= n; p += p & -p) {
    c[p] += x;
  }
}
int ask(int p) {
  int r = 0;
  for (; p; p -= p & -p) {
    r += c[p];
  }
  return r;
}
}
int st[Q];

void solve(int l, int r) {
  if (l == r) return;
  int m = l + r >> 1;
  solve(l, m), solve(m + 1, r);
  using namespace Fenwick;
  int i = l, j = m + 1;
  for (int k = l; k <= r; k++) {
    if (j > r || i <= m && s[i] < s[j]) {
      if (!s[i].id) {
        add(s[i].l, s[i].r);
        st[++st[0]] = i;
      }
      t[k] = s[i++];
    } else {
      if (s[j].id) {
        ans[s[j].id] += ask(s[j].r) - ask(s[j].l - 1);
      }
      t[k] = s[j++];
    }
  }
  while (st[0]) {
    i = st[st[0]--];
    add(s[i].l, -s[i].r);
  }
  for (int k = l; k <= r; k++) s[k] = t[k];
}

void add(int p, int i) {
  s[++cnt] = {p, i, 1, 0};
}
void del(int p, int i) {
  s[++cnt] = {p, i, -1, 0};
}
void ask(int l, int r, int id) {
  s[++cnt] = {l - 1, l, r, id};
}

std::set<int>::iterator split(int x) {
  auto u = p.insert(x);
  if (u.second) {
    int p = *std::prev(u.first);
    a[x] = a[p];
    auto &v = g[a[x]];
    auto it = v.insert(x).first;
    add(p, x);
    if (++it != v.end()) del(p, *it), add(x, *it);
  }
  return u.first;
}


void solve() {
  std::cin >> n >> m;
  for (int i = 1; i <= n; i++) {
    std::cin >> a[i];
    p.insert(i);
  }
  p.insert(n + 1);

  for (int i = 1; i <= n; i++) {
    auto &v = g[a[i]];
    auto it = v.insert(i).first;
    int p = it == v.begin() ? 0 : *std::prev(it);
    add(p, i);
  }

  for (int i = 1; i <= m; i++) {
    int opt, l, r, x;
    std::cin >> opt >> l >> r;
    if (opt == 1) {
      split(++r);
      for (auto it = split(l); *it != r; it = p.erase(it)) {
        x = *it;
        auto &v = g[a[x]];
        auto t = v.find(x);
        assert(t != v.end());
        int p = t == v.begin() ? 0 : *std::prev(t);
        t = v.erase(t);
        del(p, x);
        if (t != v.end()) {
          del(x, *t);
          add(p, *t);
        }
      }
      std::cin >> x;
      auto &v = g[x];
      a[l] = x;
      p.insert(l);
      auto it = v.insert(l).first;
      int p = it == v.begin() ? 0 : *std::prev(it);
      add(p, l);
      if (++it != v.end()) {
        del(p, *it);
        add(l, *it);
      }
    } else {
      split(l);
      ask(l, r, ++la);
    }
  }
  solve(1, cnt);
  for (int i = 1; i <= la; i++) {
    std::cout << ans[i] << "\n";
  }
}

int main() {
  // freopen("t.in", "r", stdin);
  
  std::ios::sync_with_stdio(false);
  std::cin.tie(nullptr);
  
  int t = 1;
  
  // std::cin >> t;
  
  while (t--) {
    solve();
  }
  return 0;
}

P5073 [Ynoi2015] 世上最幸福的女孩

给定序列 \(a_n\)\(m\) 次操作,全局加,区间求最大子段和。

\(1 \leq n \leq 3\times 10^5, 1 \leq m \leq 6\times 10^5\)

空间 128MB。

考虑经典线段树做法维护前后缀以及答案。

对于全局加了 \(a\),设原先长度为 \(a\) 的答案为 \(b\),那么新的答案是 \(ax + b\),是经典的维护凸包问题。

对于合并左子树后缀和右子树前缀的情况,可以发现是闵可夫斯基和。

线段树维护一下几个凸包就行。

然后将询问离线,把 \(x\) 从小到大排序,就可以不用在凸包上二分。

但是这样空间炸了。

考虑可以一层一层做,在线段树上从底向上做,每次只存一层的信息即可。

但我还是没卡过去空间,所以经典卡空间方法:考虑将序列分块,每块都重新建线段树算一遍,又好写又快。

实现的时候,把线段树底层改成暴力会有很明显的优化效果。

写这个的时候,维护凸包用的是 kactl 里面那个 lineContainer 的方法,有除法,有点不优,不如直接维护算了。

// Author:  HolyK
// Created: Mon Apr 25 10:14:03 2022
#include <bits/stdc++.h>

template <class T, class U>
inline bool smin(T &x, const U &y) {
  return y < x ? x = y, 1 : 0;
}
template <class T, class U>
inline bool smax(T &x, const U &y) {
  return x < y ? x = y, 1 : 0;
}

using LL = long long;
using PII = std::pair<int, int>;

namespace {
inline char gc() {
  static constexpr int BufferSize = 1 << 22 | 5;
  static char buf[BufferSize], *p, *q;
  static std::streambuf *i = std::cin.rdbuf();
  return p == q ? p = buf, q = p + i->sgetn(p, BufferSize), p == q ? EOF : *p++ : *p++;
}
struct Reader {
  template <class T>
  Reader &operator>>(T &w) {
    char c, p = 0;
    for (; !std::isdigit(c = gc());) if (c == '-') p = 1;
    for (w = c & 15; std::isdigit(c = gc()); w = w * 10 + (c & 15)) ;
    if (p) w = -w;
    return *this;
  }
} cin;

constexpr int N(3e5 + 5), B(N >> 2), T(16);
constexpr LL INF(LLONG_MAX);
struct Line {
  int x;
  LL y, p;
  bool operator<(LL r) const { return p < r; }

  LL eval(LL z) { return x * z + y; } 
};
using C = std::vector<Line>;

LL div(LL a, LL b) {
  return a / b - ((a ^ b) < 0 && a % b);
}
void get(Line &a, const Line &b) {
  if (a.x == b.x) {
    a.p = a.y > b.y ? INF : -INF;
  } else {
    a.p = div(a.y - b.y, b.x - a.x);
  }
}
void add(C &c, int x, LL y) {
  Line o = {x, y, INF};
  if (c.empty()) {
    c.push_back(o);
    return;
  }
  while (c.size() > 1) {
    get(c.back(), o);
    if (c[c.size() - 2].p < c.back().p) break;
    c.pop_back();
  }
  get(c.back(), o);
  c.push_back(o);
}


int n, m, a[N];
LL b[N];
struct Node {
  C pre, suf, ans;
  LL sum;
  int pl, sl, al;
} t[B >> 2];
#define ls o << 1
#define rs o << 1 | 1

C msum(const C &a, const C &b) {
  C c = {Line{a[0].x + b[0].x, a[0].y + b[0].y, INF}};
  for (int i = 0, j = 0; i + 1 < a.size() || j + 1 < b.size(); ) {
    Line &o = c.back();
    if (j + 1 == b.size() || i + 1 < a.size() && a[i].p < b[j].p) {
      add(c, o.x + a[i + 1].x - a[i].x, o.y + a[i + 1].y - a[i].y);
      i++;
    } else {
      add(c, o.x + b[j + 1].x - b[j].x, o.y + b[j + 1].y - b[j].y);
      j++;
    }
  }
  return c;
}
C merge(const C &a, const C &b) {
  C c;
  for (int i = 0, j = 0; i < a.size() || j < b.size(); ) {
    if (j == b.size() || i < a.size() && a[i].x < b[j].x) {
      add(c, a[i].x, a[i].y);
      i++;
    } else {
      add(c, b[j].x, b[j].y);
      j++;
    }
  }
  return c;
}

void build(int o, int l, int r) {
  t[o].pl = t[o].sl = t[o].al = 0;
  if (r - l + 1 <= T) {
    t[o].pre.clear();
    t[o].suf.clear();
    t[o].ans.clear();
    LL s = 0;
    add(t[o].pre, 0, 0);
    for (int i = 1; i <= r - l + 1; i++) {
      s += a[l + i - 1];
      add(t[o].pre, i, s);
    }
    s = 0;
    add(t[o].suf, 0, 0);
    for (int i = 1; i <= r - l + 1; i++) {
      s += a[r - i + 1];
      add(t[o].suf, i, s);
    }
    add(t[o].ans, 0, 0);
    for (int i = 1; i <= r - l + 1; i++) {
      LL max = -INF;
      for (int j = l + i - 1; j <= r; j++) {
        smax(max, b[j] - b[j - i]);
      }
      add(t[o].ans, i, max);
    }
    t[o].sum = s;
    return;
  }
  int m = l + r >> 1;
  build(ls, l, m);
  build(rs, m + 1, r);

  t[o].pre = t[ls].pre;
  for (auto &[x, y, _] : t[rs].pre) {
    add(t[o].pre, x + m - l + 1, y + t[ls].sum);
  }
  t[o].suf = t[rs].suf;
  for (auto &[x, y, _] : t[ls].suf) {
    add(t[o].suf, x + r - m, y + t[rs].sum);
  }
  t[o].ans = merge(merge(t[ls].ans, t[rs].ans), msum(t[ls].suf, t[rs].pre));
  t[o].sum = t[ls].sum + t[rs].sum;
}

struct Info {
  LL r, a;
  void apply(LL pre, LL suf, LL ans, LL sum) {
    smax(a, ans);
    smax(a, r + pre);
    r = std::max(suf, r + sum);
  }
} qans[N * 2];

LL ask(const C &c, LL x) {
  auto l = std::lower_bound(c.begin(), c.end(), x);
  assert(l != c.end());
  return l->x * x + l->y;
}

void ask(int o, int l, int r, int x, int y, LL z, int id) {
  if (x <= l && r <= y) {
    auto &pl = t[o].pl, &sl = t[o].sl, &al = t[o].al;
    while (t[o].pre[pl].p < z) pl++;
    while (t[o].suf[sl].p < z) sl++;
    while (t[o].ans[al].p < z) al++;
    qans[id].apply(t[o].pre[pl].eval(z), t[o].suf[sl].eval(z), t[o].ans[al].eval(z), t[o].sum + z * (r - l + 1));
    return;
  }
  if (r - l + 1 <= T) {
    LL pre, suf, ans, sum, max = 0;
    pre = suf = ans = sum = 0;
    for (int i = std::max(x, l); i <= r && i <= y; i++) {
      sum += a[i] + z;
      smax(pre, sum);
      smax(ans, sum + max);
      smax(max, -sum);
    }
    sum = 0;
    for (int i = std::min(r, y); i >= l && i >= x; i--) {
      sum += a[i] + z;
      smax(suf, sum);
    }
    qans[id].apply(pre, suf, ans, sum);
    return;
  }
  int m = l + r >> 1;
  if (x <= m) ask(ls, l, m, x, y, z, id);
  if (y > m) ask(rs, m + 1, r, x, y, z, id);
}


struct Qry {
  int l, r, id;
  LL x;
  bool operator<(const Qry &rhs) const {
    return x < rhs.x;
  }
} q[N * 2];
int qq;
void solve() {
  cin >> n >> m;
  for (int i = 1; i <= n; i++) cin >> a[i], b[i] = a[i] + b[i - 1];
  LL d = 0;
  while (m--) {
    int opt, l, r;
    cin >> opt;
    if (opt == 1) {
      cin >> l;
      d += l;
    } else {
      cin >> l >> r;
      q[qq] = {l, r, qq, d};
      qq++;
    }
  }
  std::sort(q, q + qq);
  for (int l = 1, r; l <= n; l += B) {
    r = std::min(l + B - 1, n);
    build(1, l, r);
    for (int i = 0; i < qq; i++) {
      if (q[i].r < l || q[i].l > r) continue;
      ask(1, l, r, q[i].l, q[i].r, q[i].x, q[i].id);
    }
  }
  for (int i = 0; i < qq; i++) {
    std::cout << qans[i].a << "\n";
  }
}
}

int main() {
  // freopen("t.in", "r", stdin);
  
  std::ios::sync_with_stdio(false);
  std::cin.tie(nullptr);
  
  int t = 1;
  
  // std::cin >> t;
  
  while (t--) {
    solve();
  }
  return 0;
}

P4118 [Ynoi2018] 末日时在做什么?有没有空?可以来拯救吗?

给定序列 \(a_n\)\(m\) 次操作,区间加,区间求最大子段和。

\(1 \leq n, m \leq 10^5\)

空间 64 MB。

上面那题的进阶版本,1秒时限太毒瘤了,卡不过去,所以这题是 80 分。

顺着上题思路,分块之后每块处理。

修改和询问会变成 \(O(m\sqrt n)\) 次全局操作和 \(O(m)\) 次区间操作。

对于全局加全局查询,按照上题的做法,把询问按照 \(x\) 基数排序,每次做凸包指针移动次数是 \(O(\sqrt n)\)

对于区间修改,会使得把全局查询的凸包指针失效,但是只会修改 \(O(m)\) 次,所以每次修改之后重置指针,复杂度是 \(O(m\sqrt n)\)

区间修改,在线段树上更新凸包,这样做的复杂度是 \(O(\sqrt n+ \sqrt n/2+ \sqrt n/4+\dots) = O(\sqrt n)\),所以每次直接在线段树上搞,复杂度是 \(O(m\sqrt n)\)

区间查询,每次查询可以是 \(O(\sqrt n)\) 的,但是这样写可能常数比较大,我写的是在线段树上查询,每个凸包二分一下,复杂度是 \(O(m \log^2 n)\)

线段树底层仍然要暴力。

对于信息合并,直接归并的复杂度太高了,观察这个凸包,发现 \(x\) 坐标的范围是 \(1 \dots len\),所以说可以类似桶排序,对于每个 \(x\) 坐标存一下 \(y\) 坐标的最大值,这样 cache 友好,卡常效果明显。

这个信息,本来是用 vector 存,后面卡常把 vector 扔掉了,最后还是没卡过去,真是尽力了。

// Author:  HolyK
// Created: Wed Apr 27 14:32:40 2022
#include <bits/stdc++.h>

inline char gc() {
  static constexpr int BufferSize = 1 << 22 | 5;
  static char buf[BufferSize], *p, *q;
  static std::streambuf *i = std::cin.rdbuf();
  return p == q ? p = buf, q = p + i->sgetn(p, BufferSize), p == q ? EOF : *p++ : *p++;
}
struct Reader {
  template <class T>
  Reader &operator>>(T &w) {
    char c, p = 0;
    for (; !std::isdigit(c = gc());) if (c == '-') p = 1;
    for (w = c & 15; std::isdigit(c = gc()); w = w * 10 + (c & 15)) ;
    if (p) w = -w;
    return *this;
  }
} cin;

template <class T, class U>
inline bool smin(T &x, const U &y) {
  return y < x ? x = y, 1 : 0;
}
template <class T, class U>
inline bool smax(T &x, const U &y) {
  return x < y ? x = y, 1 : 0;
}

using LL = long long;
using PII = std::pair<int, int>;

constexpr int N(1e5 + 5), T(8), B(T << 6);
constexpr LL INF(LLONG_MAX / 3);
struct Pt {
  int x;
  LL y;
  Pt operator+(const Pt &r) const { return {x + r.x, y + r.y}; }
  Pt operator-(const Pt &r) const { return {x - r.x, y - r.y}; }
  LL operator*(const Pt &r) const { return x * r.y - y * r.x; }
  LL cross(const Pt &a, const Pt &b) const { return (a - *this) * (b - *this); }
  LL eval(LL z) const { return x * z + y; }
} pt[B * 30], s[B + 5];
int ptr;

struct Hull {
  int l, r;
  Pt *begin() const { return pt + l; }
  Pt *data() const { return begin(); }
  Pt *end() const { return pt + r; }
  int size() const { return r - l; }
  void assign(Pt *s, Pt *e) {
    r = l + (e - s);
    memcpy(pt + l, s, (e - s) * sizeof(*s));
  }
  Pt &operator[](const int &i) const {
    // assert(0 <= i && i < size());
    return pt[l + i];
  }
};
using C = Hull;
// using C = std::vector<Pt>;

void fwd(const C &c, int &p, LL x) {
  while (p + 1 < c.size() && c[p].y - c[p + 1].y < x * (c[p + 1].x - c[p].x)) p++;
}

int top;
void add(Pt x) {
  while (top > 1 && s[top - 2].cross(s[top - 1], x) >= 0) top--;
  s[top++] = x;
}

LL val[B + 5];
void update(const Pt &x) { smax(val[x.x], x.y); }
void cal(int n) {
  top = 0;
  for (int i = 1; i <= n; i++) if (val[i] != -INF) add({i, val[i]});
}

struct Node {
  C pre, suf, ans;
  LL sum;
} t[B << 2];
LL tag[B << 2];
#define ls o << 1
#define rs o << 1 | 1
void pushup(int o, int len) {
  int ll = len - len / 2, rr = len / 2, tmp = ptr;
  LL l = t[ls].sum + tag[ls] * ll, r = t[rs].sum + tag[rs] * rr;
  C lsuf, rpre;
  lsuf.l = ptr -= t[ls].suf.size();
  lsuf.assign(t[ls].suf.begin(), t[ls].suf.end());
  rpre.l = ptr -= t[rs].pre.size();
  rpre.assign(t[rs].pre.begin(), t[rs].pre.end());
  // auto lsuf = t[ls].suf, rpre = t[rs].pre;
  
  // pre
  top = t[ls].pre.size();
  memcpy(s, t[ls].pre.data(), top * sizeof(*s));
  for (int i = 0; i < top; i++) s[i].y += s[i].x * tag[ls];
  int cur = top;
  for (auto &[x, y] : rpre) {
    y += x * tag[rs];
    s[cur++] = {x + ll, y + l};
  }
  for (int i = top; i < cur; i++) add(s[i]);
  t[o].pre.assign(s, s + top);

  // suf
  top = t[rs].suf.size();
  memcpy(s, t[rs].suf.data(), top * sizeof(*s));
  for (int i = 0; i < top; i++) s[i].y += s[i].x * tag[rs];
  cur = top;
  for (auto &[x, y] : lsuf) {
    y += x * tag[ls];
    s[cur++] = {x + rr, y + r};
  }
  for (int i = top; i < cur; i++) add(s[i]);
  t[o].suf.assign(s, s + top);

  // ans: lr
  for (int i = 1; i <= len; i++) val[i] = -INF;
  for (auto &[x, y] : t[ls].ans) smax(val[x], y + x * tag[ls]);
  for (auto &[x, y] : t[rs].ans) smax(val[x], y + x * tag[rs]);

  // ans: merge
  update(lsuf[0] + rpre[0]);
  for (int i = 0, j = 0; i + 1 < lsuf.size() || j + 1 < rpre.size(); ) {
    if (j + 1 == rpre.size() || i + 1 < lsuf.size() && (lsuf[i + 1] - lsuf[i]) * (rpre[j + 1] - rpre[j]) < 0) {
      i++;
    } else {
      j++;
    }
    update(lsuf[i] + rpre[j]);
  }
  cal(len);
  t[o].ans.assign(s, s + top);
  
  t[o].sum = l + r;
  
  ptr = tmp;
}

LL a[N];
void init(int o, int l, int r) {
  LL sum = 0;
  top = 0;
  for (int i = l; i <= r; i++) {
    sum += a[i];
    add({i - l + 1, sum});
  }
  t[o].pre.assign(s, s + top);
  top = 0, sum = 0;
  for (int i = r; i >= l; i--) {
    sum += a[i];
    add({r - i + 1, sum});
  }
  t[o].suf.assign(s, s + top);
  t[o].sum = sum;
  for (int i = 1; i <= r - l + 1; i++) val[i] = -INF;
  for (int i = l; i <= r; i++) {
    sum = 0;
    for (int j = i; j <= r; j++) {
      sum += a[j];
      smax(val[j - i + 1], sum);
    }
  }
  cal(r - l + 1);
  t[o].ans.assign(s, s + top);
}

void build(int o, int l, int r) {
  t[o].pre.l = ptr -= r - l + 1;
  t[o].suf.l = ptr -= r - l + 1;
  t[o].ans.l = ptr -= r - l + 1;
  tag[o] = 0;
  if (r - l + 1 <= T) {
    init(o, l, r);
    return;
  }
  int m = l + r >> 1;
  build(ls, l, m);
  build(rs, m + 1, r);
  pushup(o, r - l + 1);
}


void update(int o, int l, int r, int x, int y, LL z) {
  if (x <= l && r <= y) {
    tag[o] += z;
    return;
  }
  if (r - l + 1 <= T) {
    // std::cerr << "updateI " << l << " " << r << " " << x << " " << y << "\n";
    for (int i = std::max(l, x); i <= r && i <= y; i++) a[i] += z;
    init(o, l, r);
    return;
  }
  int m = l + r >> 1;
  if (x <= m) update(ls, l, m, x, y, z);
  if (y > m) update(rs, m + 1, r, x, y, z);
  pushup(o, r - l + 1);
}

struct Info {
  LL r, a;
  void apply(LL pre, LL suf, LL ans, LL sum) {
    smax(a, ans);
    smax(a, r + pre);
    r = std::max(suf, r + sum);
  }
} qans[N];


LL ask(const C &c, LL x) {
  int l = 0, r = c.size() - 1;
  while (l < r) {
    int m = l + r >> 1;
    if (c[m].y - c[m + 1].y < x * (c[m + 1].x - c[m].x)) {
      l = m + 1;
    } else {
      r = m;
    }
  }
  return c[l].eval(x);
}

void ask(int o, int l, int r, int x, int y, int id, LL z) {
  z += tag[o];
  if (x <= l && r <= y) {
    qans[id].apply(ask(t[o].pre, z), ask(t[o].suf, z), ask(t[o].ans, z), t[o].sum + z * (r - l + 1));
    return;
  }
  if (r - l + 1 <= T) {
    LL pre, suf, ans, sum, max;
    pre = suf = ans = sum = max = 0;
    for (int i = std::max(x, l); i <= r && i <= y; i++) {
      sum += a[i] + z;
      smax(pre, sum);
      smax(ans, sum + max);
      smax(max, -sum);
    }
    sum = 0;
    for (int i = std::min(r, y); i >= l && i >= x; i--) {
      sum += a[i] + z;
      smax(suf, sum);
    }
    qans[id].apply(pre, suf, ans, sum);
    return;
  }

  int m = l + r >> 1;
  if (x <= m) ask(ls, l, m, x, y, id, z);
  if (y > m) ask(rs, m + 1, r, x, y, id, z);
}


struct Operation {
  int opt, l, r, x;
} op[N];
struct Qry {
  LL x;
  int id;
} q[N], nq[N];

void solve() {
  int n, m;
  cin >> n >> m;
  for (int i = 1; i <= n; i++) cin >> a[i];
  for (int i = 0; i < m; i++) {
    int opt, l, r, x = 0;
    cin >> opt >> l >> r;
    if (opt == 1) cin >> x;
    op[i] = {opt, l, r, x};
  }

  for (int l = 1; l <= n; l += B) {
    ptr = B * 30;
    int r = std::min(l + B - 1, n);
    build(1, l, r);
    int pl, sl, al, qn;

    auto init = [&] { pl = sl = al = qn = 0; };

    auto run = [&] {
      if (!qn) return;
      if (qn > 800) {
        LL min = LLONG_MAX;
        for (int i = 0; i < qn; i++) smin(min, q[i].x);
        for (int i = 0; i < qn; i++) q[i].x -= min;
        static int c[256];
#define SORT(a, b, n, d)                                                \
        memset(c, 0, sizeof c);                                         \
        for (int i = 0; i < n; i++) c[a[i].x >> d & 0xff]++;            \
        for (int i = 1; i < 256; i++) c[i] += c[i - 1];                 \
        for (int i = qn - 1; i >= 0; i--) b[--c[a[i].x >> d & 0xff]] = a[i];

        SORT(q, nq, qn, 0);
        SORT(nq, q, qn, 8);
        SORT(q, nq, qn, 16);
        SORT(nq, q, qn, 24);
        
        for (int i = 0; i < qn; i++) q[i].x += min;
      } else {
        std::sort(q, q + qn, [&](const Qry &i, const Qry &j) { return i.x < j.x; });
      }
      for (int i = 0; i < qn; i++) {
        fwd(t[1].pre, pl, q[i].x);
        fwd(t[1].suf, sl, q[i].x);
        fwd(t[1].ans, al, q[i].x);
        qans[q[i].id].apply(t[1].pre[pl].eval(q[i].x), t[1].suf[sl].eval(q[i].x), t[1].ans[al].eval(q[i].x), t[1].sum + (r - l + 1) * q[i].x);
      }
    };
    
    init();
    for (int i = 0; i < m; i++) {
      if (op[i].r < l || op[i].l > r) continue;
      if (op[i].l <= l && r <= op[i].r) {
        if (op[i].opt == 1) {
          tag[1] += op[i].x;
        } else {
          q[qn++] = {tag[1], i};
        }
      } else {
        if (op[i].opt == 1) {
          run();
          init();
          update(1, l, r, op[i].l, op[i].r, op[i].x);
        } else {
          ask(1, l, r, op[i].l, op[i].r, i, 0);
        }
      }
    }
    run();
  }
  for (int i = 0; i < m; i++) {
    if (op[i].opt == 2) {
      std::cout << qans[i].a << "\n";
    }
  }
}

int main() {
  // freopen("t.in", "r", stdin);
  // freopen("t.out", "w", stdout);
  
  std::ios::sync_with_stdio(false);
  std::cin.tie(nullptr);
  
  int t = 1;
  
  // std::cin >> t;
  
  while (t--) {
    solve();
  }

  std::cerr << (double)clock() / CLOCKS_PER_SEC << "\n";
  return 0;
}

P6783 [Ynoi2008] rrusq

给定 \(n\) 个二维平面上的点,每个点有权值,给定 \(m\) 个矩形,\(q\) 次询问第 \(l\) 到 第 \(r\) 个矩形的并覆盖的点的权值之和。

\(n, m \leq 10^5, q \leq 10^6\)

考虑一维的情况,离线之后扫描右端点,维护左端点的答案,显然每次进行区间染色,查询颜色 \(\geq l\) 的区间权值之和即可。

拓展到二维,用 leafy-KD-tree 将二维矩形变成 \(O(\sqrt n)\) 个树上节点,染色时暴力回收区间的染色标记,标记需要pushdown。

这个回收操作是染色操作的逆操作,所以操作总次数是 \(O(m\sqrt n)\),需要搞一个 \(O(x)-O(xn^{\frac1x})\) 的数据结构来维护。

// Author:  HolyK
// Created: Mon Sep 19 10:23:32 2022
#include <algorithm>
#include <bits/stdc++.h>

template <class T, class U>
inline bool smin(T &x, const U &y) {
  return y < x ? x = y, 1 : 0;
}
template <class T, class U>
inline bool smax(T &x, const U &y) {
  return x < y ? x = y, 1 : 0;
}

using LL = long long;
using PII = std::pair<int, int>;

constexpr int N(1e5 + 5), L(6), MASK((1 << L) - 1), NL(N >> L * 2);
struct SumDS {
  int s[3][N + (1 << L)];
  void add(int p, int x) {
    s[0][p] += x;
    s[1][p >>= L] += x;
    s[2][p >>= L] += x;
  }
  int ask(int p) {
    int ans = 0;
    while (p & MASK) ans += s[0][p++];
    p >>= L;
    while (p & MASK) ans += s[1][p++];
    p >>= L;
    while (p <= NL) ans += s[2][p++];
    return ans;
  }
} sumDS;

int n, m, a[N][3];
#define ls o << 1
#define rs o << 1 | 1

struct Node {
  int l[2], r[2], sum, col, vis;
} t[N << 2];

int p[N];

template <int D>
void build(int o, int l, int r) {
  t[o].col = -1;
  t[o].vis = 0;
  if (l == r) {
    t[o].sum = a[p[l]][2];
    t[o].l[0] = t[o].r[0] = a[p[l]][0];
    t[o].l[1] = t[o].r[1] = a[p[l]][1];
    return;
  }
  int m = l + r >> 1;
  std::nth_element(p + l, p + m, p + r + 1,
                   [&](int i, int j) { return a[i][D] < a[j][D]; });
  build<D ^ 1>(ls, l, m);
  build<D ^ 1>(rs, m + 1, r);
  t[o].sum = t[ls].sum + t[rs].sum;

  t[o].l[0] = std::min(t[ls].l[0], t[rs].l[0]);
  t[o].l[1] = std::min(t[ls].l[1], t[rs].l[1]);
  t[o].r[0] = std::max(t[ls].r[0], t[rs].r[0]);
  t[o].r[1] = std::max(t[ls].r[1], t[rs].r[1]);
}

void pushdown(int o) {
  if (t[o].col != -1) {
    t[ls].col = t[rs].col = t[o].col;
    t[ls].vis = t[rs].vis = 1;
    t[o].col = -1;
  }
}

void clear(int o) {
  if ((o < N * 4) && t[o].vis) {
    t[o].vis = 0;
    if (t[o].col != -1) {
      sumDS.add(t[o].col, -t[o].sum);
      t[o].col = -1;
    }
    clear(ls), clear(rs);
  }
}

void update(int o, int l, int r, const std::array<int, 4> &x, int y) {
  if (t[o].r[0] < x[0] || x[1] < t[o].l[0] || t[o].r[1] < x[2] || x[3] < t[o].l[1]) return;
  
  if (t[o].l[0] >= x[0] && x[1] >= t[o].r[0] && t[o].l[1] >= x[2] && x[3] >= t[o].r[1]) {
    clear(o);
    t[o].col = y;
    t[o].vis = 1;
    sumDS.add(y, t[o].sum);
    return;
  }

  assert(l < r);
  
  pushdown(o);
  
  int m = l + r >> 1;
  update(ls, l, m, x, y);
  update(rs, m + 1, r, x, y);
  t[o].vis = 1;
}


void solve() {
  std::cin >> n;
  for (int i = 1; i <= n; i++) {
    a[i][0] = i;
    std::cin >> a[i][1] >> a[i][2];
    
    p[i] = i;
  }

  build<0>(1, 1, n);

  std::cin >> m;
  std::vector<std::array<int, 4>> b(m);
  for (auto &v : b) std::cin >> v[0] >> v[1] >> v[2] >> v[3];

  std::cin >> m;
  std::vector<std::array<int, 3>> q(m);
  for (int i = 0, l, r; i < m; i++) {
    std::cin >> l >> r;
    q[i] = {r, l - 1, i};
  }

  std::sort(q.begin(), q.end());

  std::vector<int> ans(m);
  int i = 0;
  for (auto &[r, l, id] : q) {
    while (i < r) update(1, 1, n, b[i], i), i++;
    ans[id] = sumDS.ask(l);
  }

  for (auto &z : ans) std::cout << z << "\n";
}

int main() {
  // freopen("t.in", "r", stdin);
  
  std::ios::sync_with_stdio(false);
  std::cin.tie(nullptr);
  
  int t = 1;
  
  // std::cin >> t;
  
  while (t--) {
    solve();
  }
  return 0;
}

P6109 [Ynoi2009] rprmq1

\(n\times n\) 平面,先做 \(m\) 次矩形加,再 \(q\) 次矩形求最大值

\(n, m\leq 50000, q \leq 5\times 10^5\)

转换成查询版本区间 \([l_1, r_1]\) 在区间 \([l_2, r_2]\) 的最大值。

如果是求和,那是可减的,写个主席树减一下就行了。

但是求最大值,没办法减,考虑给他拆成一个前缀和一个后缀的答案。

分治,每次处理跨区间中点的询问。

然后就是一个线段树维护历史最大值,有一些细节,在处理前缀集合 \([mid, i]\) 之前,需要把历史最大值清空,然后每次把所有右端点为 \(i\) 的操作全部加完之后再贡献到历史最大值上面,不然先加后减答案会错。

这个分治操作可以用位运算转成非递归的。

constexpr LL NINF = -1e18;
struct Tag {
  LL a = 0, b = NINF, c = 0;
  void apply(const Tag &r) {
    b = std::max(b + r.c, a + r.b);
    a += r.a, c += r.c;
    smax(b, NINF);
    smax(c, NINF);
  }
};

struct Info {
  LL max = 0, hmax = 0;
  void apply(const Tag &r) {
    hmax = std::max(max + r.b, hmax + r.c);
    max += r.a;
  }
  Info operator+(const Info &r) const {
    return {std::max(max, r.max), std::max(hmax, r.hmax)};
  }
};

using SegTree = LazySegmentTree<Info, Tag>;

void solve() {
  int n, m, q;
  cin >> n >> m >> q;
  std::vector<int> co(n + 1);
  std::vector<std::array<int, 3>> op(m * 2);
  
  {
    std::vector<std::array<int, 5>> t(m);
    for (int i = 0; i < m; i++) {
      int l1, l2, r1, r2, x;
      cin >> l1 >> l2 >> r1 >> r2 >> x;
      t[i] = {--l1, --l2, r1, r2, x};
      co[l1]++, co[r1]++;
    }

    for (int i = 1; i <= n; i++) co[i] += co[i - 1];

    for (auto &[l1, l2, r1, r2, x] : t) {
      op[--co[l1]] = {l2, r2, x};
      op[--co[r1]] = {l2, r2, -x};
    }
  }
  
  std::vector<std::array<int, 5>> qry(q);
  std::vector<int> cq(n + 1), seq(q);
  for (int i = 0; i < q; i++) {
    int k, l1, l2, r1, r2;
    cin >> l1 >> l2 >> r1 >> r2;
    l1--, l2--, r1--;
    if (l1 == r1) {
      k = r1;
    } else {
      k = 1 << std::__lg(l1 ^ r1);
      k = r1 & -k;
    }
    qry[i] = {k, l1, r1, l2, r2};

    // std::cerr << l1 << " " << r1 << " " << l2 << " " << r2 << " " << k << "\n";
    cq[k]++;
  }

  std::vector<LL> ans(q);

  // R
  std::vector<int> p(q);
  std::iota(p.begin(), p.end(), 0);
  std::sort(p.begin(), p.end(), [&](int i, int j) {
    return qry[i][2] > qry[j][2];
  });
  for (int i = 1; i <= n; i++) cq[i] += cq[i - 1];
  for (int i : p) seq[--cq[qry[i][0]]] = i;

  SegTree seg(n);
  for (int o = 0; o < n; o++) {
    int r = o ? std::min(o + (o & -o), n) : 1;

    int k = cq[o];
    
    for (int i = o; i < r; i++) {
      for (int j = co[i]; j < co[i + 1]; j++) {
        seg.rangeApply(op[j][0], op[j][1], {op[j][2], NINF, 0});
      }
      seg.rangeApply(0, n, {0, 0, 0});
      while (k < cq[o + 1] && qry[seq[k]][2] == i) {
        int l = qry[seq[k]][3], r = qry[seq[k]][4];
        smax(ans[seq[k++]], seg.rangeQuery(l, r).hmax);
      }
    }

    assert(k == cq[o + 1]);

    for (int j = co[o + 1]; j < co[r]; j++) {
      seg.rangeApply(op[j][0], op[j][1], {-op[j][2], 0, 0});
    }
    seg.rangeApply(0, n, {0, NINF, NINF});
  }

  // L 
  std::sort(p.begin(), p.end(), [&](int i, int j) {
    return qry[i][1] < qry[j][1];
  });
  for (int i = 1; i <= n; i++) cq[i - 1] = cq[i];
  for (int i : p) seq[--cq[qry[i][0]]] = i;

  for (int o = n - 1; o > 0; o--) {
    int l = o & o - 1;
    
    int k = cq[o];
    while (k < cq[o + 1] && qry[seq[k]][1] == o) {
      k++;
    }
    for (int i = o - 1; i >= l; i--) {
      for (int j = co[i + 1]; j < co[i + 2]; j++) {
        seg.rangeApply(op[j][0], op[j][1], {-op[j][2], NINF, 0});
      }
      seg.rangeApply(0, n, {0, 0, 0});

      while (k < cq[o + 1] && qry[seq[k]][1] == i) {
        int l = qry[seq[k]][3], r = qry[seq[k]][4];
        smax(ans[seq[k++]], seg.rangeQuery(l, r).hmax);
      }
    }

    assert(k == cq[o + 1]);

    for (int j = co[l + 1]; j < co[o]; j++) {
      seg.rangeApply(op[j][0], op[j][1], {op[j][2], 0, 0});
    }
    seg.rangeApply(0, n, {0, NINF, NINF});
  }

  for (auto &z : ans) cout << z << "\n";
}

P8211 [THUPC2022 初赛] 搬砖

\(n\) 摞砖,你有精力值 \(d\)。每小时,你会搬走每一摞砖最上面的 \(d\) 块(不足则全部搬走),一摞砖有 \(a\) 块,还有属性 \(b\)搬完这一摞砖后,精力值就会下降 \(b\)。如果没有任何一摞砖被搬完或者精力值下降到 \(0\) 或以下,你就会停止工作。如果你发现自己需要工作但是所有的砖已被搬完,这一小时仍算作工作时间。有 \(T\) 次操作,每次操作加一摞砖,给出精力值 \(d\) 并询问工作时间。

\(T\le 351493,1\le op\le 2,1\le a\le 100000,0\le b\le 100000,1\le d \le 100000\)

\(x\) 表示当前精力值。

如何判断没有任何一摞砖被搬完:维护一个 \(now\) 表示已经搬了的砖的数量,只需要检查 \((now, now+x]\) 内有没有砖即可。

如果 \(b > 0\) 则答案是根号级别: 令 \(B = \sqrt{N}\) (\(N=1000000\)),如果 \(x \geq B\),那么最多搬 \(N / B\) 轮。

如果 \(x < B\),发现每轮 \(x\) 至少减 1,最多 \(x\) 轮结束。

但是有 \(b = 0\) 的情况,这会导致一轮下来可能 \(x\) 没变。 这样的话,\(now\) 会一直 += \(x\),考虑对每个 \(x < B\) 开一个并查集,\(j \to j+x\) 当且仅当 \((j, j + x]\) 有砖块。每轮搞之前先拿并查集跳一下。

对于并查集,如果每次暴力维护则是 \(O(n)\) 的,但是显然可以用一些简单的操作让每个 \(j\to j+x\) 的边只会被考虑 \(O(1)\) 次。

需要搞一堆 \(O(\sqrt N)\) - \(O(1)\) 的数据结构。

constexpr int N(1e5 + 5), T(256), S(N / T + 1);

template <class Tp>
struct PSumDS {
  Tp s0[N], s1[S];
  void add(int x, Tp y) {
    int k = x / T;
    for (int i = x; i < k * T + T && i < N; i++) s0[i] += y;
    for (int i = k + 1; i < S; i++) s1[i] += y;
  }
  Tp ask(int x) {
    smin(x, 1e5);
    return s0[x] + s1[x / T];
  }
  Tp ask(int l, int r) {
    return ask(r) - ask(l);
  } 
};

struct SMinDS {
  int m0[N], m1[S];
  SMinDS() {
    memset(m0, 0x3f, sizeof m0);
    memset(m1, 0x3f, sizeof m1);
  }
  void add(int x, int y) {
    int k = x / T;
    for (int i = k * T; i <= x; i++) smin(m0[i], y);
    for (int i = 0; i < k; i++) smin(m1[i], y);
  }
  int ask(int x) {
    return std::min(m0[x], m1[x / T]);
  }
};

struct DSU {
  int f[N + T];
  void init(int n) {
    for (int i = 0; i <= n; i++) f[i] = i;
  }
  int find(int x) {
    while (x != f[x]) x = f[x] = f[f[x]];
    return x;
  }
};

PSumDS<int> cnt;
PSumDS<LL> sum;
SMinDS next;
DSU d[T];
void solve() {
  for (int i = 1; i < T; i++) {
    d[i].init(1e5 + T);
  }
  
  int t;
  std::cin >> t;
  while (t--) {
    int opt;
    std::cin >> opt;
    if (opt == 1) {
      int x, y;
      std::cin >> x >> y;
      cnt.add(x, 1);
      sum.add(x, y);
      if (y) next.add(x, x);
      
      int k = x;
      while (k < x + T && cnt.ask(k, k + 1) == 0) k++;
      
      for (int i = 1; i < T; i++) {
        for (int j = std::min(x - 1, k - i); j >= 0 && j >= x - i && d[i].f[j] == j; j--) {
          d[i].f[j] = j + i;
        }
      }
    } else {
      LL x;
      std::cin >> x;
      int ans = 0, now = 0;

      while (x > 0) {
        ans++;
        if (cnt.ask(now, now + x) == 0) break;
        if (x < T) {
          int y = std::min(next.ask(now + 1), d[x].find(now));
          int k = (y - 1 - now) / x;
          ans += k;
          assert(sum.ask(now, now + x * k) == 0);
          now += x * k;
        }
        now += x;
        x -= sum.ask(now - x, now);
      }
      std::cout << ans << "\n";
    }
  }
}

Just Another Data Structure Problem

给定 \(n,m\),以及序列 \(a_{1}, a_{2}, \cdots, a_{n}\)\(1,2,\cdots ,n\) 的排列 \(y_{1},y_{2},\cdots,y_{n}\),你需要回答 \(m\) 个询问。对每个询问, 给定 \(l,r\), 查询: \(\sum_{i=1}^{n}\sum_{j=i+1}^{n}[a_{i}=a_{j}]\cdot\prod_{k=i}^{j}[l\leq y_{k}\leq r]\)

\(n \leq 10^5, m \leq 10^6\)

仔细看一下这个式子,可以发现是求,对于每个 \(l \leq y \leq r\) 的连续段,求 \(\sum \binom{c_i}{2}\)

然后看一下这个 \(m\) 的范围,肯定是经典的 \(O(x)\) - \(O(xn^{1/x})\) 的多层分块了。

对于每一种 \(a\),分别计算,设 \(a_i = x\) 的出现位置 \(i\)\(p_1, p_2, \dots, p_k\)

\(L_i = \min\{a[p_i:p_{i+1}]\}, R_i = \max\{a[p_i:p_{i+1}]\}\),那合法要求是 \(l \leq L_i, R_i \leq r\)

下面按照 \(k\) 的大小进行根号分治。令 \(B = 2000\)

如果 \(k < B\),考虑暴力,把每个 \([1, k]\) 的子区间的 \(\min L_i, \max R_i\) 贡献到答案。这个子区间的总数是 \(\sum k^2 = O(nB)\)

用单调栈(即笛卡尔树)处理每个 \(L\) 对应的区间,最后扫描线求答案,这部分的复杂度是 \(O(xnB + mn^{1/x})\)

如果 \(k \geq B\),把 \(L, R\) 离散化,然后是经典的用链表维护连续段的问题,可以用回滚莫队解决。这部分的复杂度是 \(O(\sum k\sqrt m) = O(n\sqrt m + m \frac{n}{B})\)

总结:如果我们需要做询问次数为 \(m\) 的若干次莫队,莫队序列总长度为 \(n\),则可以根号分治,小的用其他算法处理,大的直接莫队。

constexpr int N(1e5 + 5), M(1e6 + 5);
struct SumDS {
  int s0[N], s1[N], s2[N];
  void add(int p, int x) {
    s0[p] += x;
    s1[p >> 5] += x;
    s2[p >> 10] += x;
  }
  int ask(int p) {
    int r = 0, b2 = p >> 10, b1 = p >> 5;
    for (int i = 0; i < b2; i++) r += s2[i];
    for (int i = b2 << 5; i < b1; i++) r += s1[i];
    for (int i = b1 << 5; i <= p; i++) r += s0[i];
    return r;
  }
} sumDS;
int n, m, a[N], b[N], min[17][N], max[17][N], lg[N];
std::vector<int> g[N];
struct Qry {
  int l, r, id;
} lq[M], rq[M];
int ql[M], qr[M];
LL ans[M];
struct SuccDS {
  char c[N];
  int l[N], r[N];
  LL sum;
  void link(int x) {
    sum += (x - l[x] + 1LL) * (r[x + 1] - x);
    r[l[x]] = r[x + 1], l[r[x + 1]] = l[x];
  }
  void cut(int x) {
    r[l[x]] = x, l[r[x + 1]] = x + 1;
  }
  void add(int i) {
    if (++c[i] == 2) {
      l[i] = r[i] = i;
      sum++;
      if (i && c[i - 1] == 2) link(i - 1);
      if (c[i + 1] == 2) link(i);
    }
  }
  void del(int i) {
    if (c[i] == 2) {
      if (i && c[i - 1] == 2) cut(i - 1);
      if (c[i + 1] == 2) cut(i);
    }
    c[i]--;
  }
  void clear(int k) {
    memset(c, 0, k);
    sum = 0;
  }
} succDS;
void solve(std::vector<PII> &p) {
  static constexpr double Alpha = 1.8;
  int k = p.size();
  int t = 1.0 * k / sqrt(m) * Alpha;
  std::vector<int> lp(k), rp(k);
  for (int i = 0; i < k; i++) {
    lp[i] = rp[i] = i;
  }
  std::sort(lp.begin(), lp.end(), [&](int i, int j) {
    return p[i].first < p[j].first;
  });
  std::sort(rp.begin(), rp.end(), [&](int i, int j) {
    return p[i].second < p[j].second;
  });
  int ptr = 1;
  for (int i = 0; i < k; i++) {
    while (ptr <= m && lq[ptr].l <= p[lp[i]].first) ql[lq[ptr++].id] = i;
  }
  while (ptr <= m) ql[lq[ptr++].id] = k;
  ptr = m;
  for (int i = k - 1; i >= 0; i--) {
    while (ptr && rq[ptr].r >= p[rp[i]].second) qr[rq[ptr--].id] = i;
  }
  while (ptr) qr[rq[ptr--].id] = -1; 
  std::vector<int> cnt((k - 1) / t + 1);
  std::vector<std::vector<int>> g((k - 1) / t + 1);
  for (int i = 1; i <= m; i++) {
    int id = lq[m - i + 1].id;
    if (ql[id] < k && qr[id] >= 0) cnt[qr[id] / t]++;
  }
  for (int i = 0; i < g.size(); i++) {
    g.reserve(cnt[i]);
  }
  for (int i = 1; i <= m; i++) {
    int id = lq[m - i + 1].id;
    if (ql[id] < k && qr[id] >= 0) g[qr[id] / t].push_back(id);
  }
  for (int i = 0; i < g.size(); i++) {
    if (g[i].empty()) continue;
    int bound = i * t, j = k - 1;   
    for (int j = 0; j < bound; j++) succDS.add(rp[j]);
    for (auto id : g[i]) {
      while (j >= ql[id]) succDS.add(lp[j--]);
      LL pre = succDS.sum;
      for (int k = bound; k <= qr[id]; k++) succDS.add(rp[k]);
      ans[id] += succDS.sum;
      succDS.sum = pre;
      for (int k = qr[id]; k >= bound; k--) succDS.del(rp[k]);
    }
    succDS.clear(k);
  }
}
PII val[N], *cur = val;
struct Node {
  PII *l, *m, *r;
};
std::vector<Node> t[N];
void build(PII *l, PII *r) {
  if (r - l <= 0) return;
  PII *m = std::min_element(l, r);
  t[m->first].push_back({l, m, r});
  build(l, m), build(m + 1, r);
}
int main() {
  std::ios::sync_with_stdio(false);
  std::cin.tie(nullptr);
  cin >> n >> m;
  for (int i = 1; i <= n; i++) {
    cin >> a[i];
    g[a[i]].push_back(i);
  }
  for (int i = 1; i <= n; i++) {
    cin >> b[i];
    min[0][i] = max[0][i] = b[i];
  }
  for (int i = 1, l, r; i <= m; i++) {
    cin >> l >> r;
    lq[i] = rq[i] = {l, r, i};
  }
  std::sort(lq + 1, lq + 1 + m, [](Qry i, Qry j) { return i.l < j.l; });
  std::sort(rq + 1, rq + 1 + m, [](Qry i, Qry j) { return i.r < j.r; });
  for (int i = 2; i <= n; i++) {
    lg[i] = lg[i >> 1] + 1;
  }
  for (int i = 1; i < 17; i++) {
    for (int j = 1 << i; j <= n; j++) {
      min[i][j] = std::min(min[i - 1][j], min[i - 1][j - (1 << i - 1)]);
      max[i][j] = std::max(max[i - 1][j], max[i - 1][j - (1 << i - 1)]);
    }
  }
  for (int i = 1; i <= n; i++) {
    if (g[i].size() <= 1) continue;
    std::vector<PII> p;
    for (int j = 1; j < g[i].size(); j++) {
      int l = g[i][j - 1], r = g[i][j], k = lg[r - l + 1];
     
      p.push_back({
          std::min(min[k][r], min[k][l + (1 << k) - 1]),
          std::max(max[k][r], max[k][l + (1 << k) - 1])
        });
    }
    if (p.size() >= 2048) {
      solve(p);
    } else {
      std::copy(p.begin(), p.end(), cur);
      build(cur, cur + p.size());
      cur += p.size();
    }
  }
  
  for (int i = n, j = m; i && j; i--) {
    for (auto &p : t[i]) {
      int max = p.m->second;
      for (auto l = p.m; l >= p.l; l--) {
        smax(max, l->second);
        int now = max;
        for (auto r = p.m; r < p.r; r++) {
          smax(now, r->second);
          sumDS.add(now, 1);
        }
      }
    }
    for (; j && lq[j].l == i; j--) {
      ans[lq[j].id] += sumDS.ask(lq[j].r);
    }
  }
  for (int i = 1; i <= m; i++) {
    std::cout << ans[i] << "\n";
  }
  return 0;
}

P4689 [Ynoi2016] 这是我自己的发明

\(n\) 个点的树,有点权,初始根是 1,\(m\) 次操作,换根为 \(x\);给两个点 \(x, y\),求从 \(x\) 子树内和 \(y\) 子树中分别选一个点,求两个点点权相等的情况数。

\(1 \leq n \leq 10^5, 1 \leq m \leq 5\times 10^5, 1 \leq a_i \leq 10^9\)

假换根,用dfs序搞一下,显然可以转换成序列问题。然后序列上答案就是 \(\sum \operatorname{cnt}(l_1, r_1, x)\cdot \operatorname{cnt}(l_2, r_2, x)\),拆成两个前缀和相减的形式,可以变成 4 个询问,这个很好用莫队维护。

然后你换根的话会可能会拆成4个询问,但是你发现换根可以拆成前缀或者后缀,那你莫队的时候可以把前缀和后缀答案都维护一下。

constexpr int N(1e5 + 5);

int n, m, a[N], b[N], up[20][N], in[N], out[N], dep[N];
std::vector<int> g[N];
void dfs(int x) {
  in[x] = ++in[0];
  b[in[0]] = a[x];
  for (int y : g[x]) {
    if (y == up[0][x]) continue;
    up[0][y] = x;
    dep[y] = dep[x] + 1;
    dfs(y);
  }
  out[x] = in[0];
}
int jmp(int x, int y) {
  assert(dep[x] > dep[y]);
  for (int i = 0; dep[x] - dep[y] > 1; i++) {
    if (dep[x] - dep[y] - 1 >> i & 1) x = up[i][x];
  }
  return x;
}

struct Qry {
  int l, r, u, v, id;
  bool operator<(const Qry &rhs) const {
    return l / 512 == rhs.l / 512 ? r < rhs.r : l < rhs.l;
  }
};
int main() {
  std::ios::sync_with_stdio(false);
  std::cin.tie(nullptr);
  std::cin >> n >> m;
  for (int i = 1; i <= n; i++) std::cin >> a[i];
  std::vector<int> v(a + 1, a + 1 + n);
  std::sort(v.begin(), v.end());
  for (int i = 1; i <= n; i++)
    a[i] = std::lower_bound(v.begin(), v.end(), a[i]) - v.begin();
  for (int i = 1, x, y; i < n; i++) {
    std::cin >> x >> y;
    g[x].push_back(y);
    g[y].push_back(x);
  }
  dfs(1);
  for (int i = 1; i < 20; i++) {
    for (int j = 1; j <= n; j++) {
      up[i][j] = up[i - 1][up[i - 1][j]];
    }
  }
  int root = 1;
  std::vector<LL> ans(m);
  std::vector<Qry> q;
  for (int i = 0; i < m; i++) {
    int opt, x, y;
    std::cin >> opt >> x;
    if (opt == 1) {
      root = x;
      ans[i] = -1e9;
    } else {
      std::cin >> y;
      auto get = [&](int x) -> std::vector<PII> {
        if (root == x) return {PII(n, 0)};
        if (in[x] <= in[root] && in[root] <= out[x]) {
          x = jmp(root, x);
          return {PII(in[x] - 1, 0), PII(out[x] + 1, 1)};
        } else {
          return {PII(-in[x] + 1, 0), PII(out[x], 0)};
        }
      };
      auto vx = get(x), vy = get(y);
      for (auto [l, u] : vx) {
        int cx = l < 0 ? l = -l, -1 : 1;
        if (l < 1 || l > n) continue;
        for (auto [r, v] : vy) {
          int cy = r < 0 ? r = -r, -1 : 1;
          if (r < 1 || r > n) continue;
          q.push_back({l, r, u, v, (i + 1) * cx * cy});
        }
      }
    }
  }
  std::sort(q.begin(), q.end());
  int x = 1, y = 1;
  std::vector<std::array<int, 2>> cl(n), cr;
  cl[b[1]][0]++;
  for (int i = 1; i <= n; i++) cl[b[i]][1]++;
  LL now[2][2];
  cr = cl;
  for (int i = 0; i < 2; i++) {
    for (int j = 0; j < 2; j++) {
      now[i][j] = 0;
      for (int k = 0; k < n; k++) {
        now[i][j] += 1LL * cl[k][i] * cr[k][j];
      }
    }
  }
  for (auto [l, r, u, v, id] : q) {
    while (x < l) {
      cl[b[x]][1]--;
      now[1][0] -= cr[b[x]][0];
      now[1][1] -= cr[b[x]][1];
      cl[b[++x]][0]++;
      now[0][0] += cr[b[x]][0];
      now[0][1] += cr[b[x]][1];
    }
    while (x > l) {
      cl[b[x]][0]--;
      now[0][0] -= cr[b[x]][0];
      now[0][1] -= cr[b[x]][1];
      cl[b[--x]][1]++;
      now[1][0] += cr[b[x]][0];
      now[1][1] += cr[b[x]][1];
    }
    while (y < r) {
      cr[b[y]][1]--;
      now[0][1] -= cl[b[y]][0];
      now[1][1] -= cl[b[y]][1];
      cr[b[++y]][0]++;
      now[0][0] += cl[b[y]][0];
      now[1][0] += cl[b[y]][1];
    }
    while (y > r) {
      cr[b[y]][0]--;
      now[0][0] -= cl[b[y]][0];
      now[1][0] -= cl[b[y]][1];
      cr[b[--y]][1]++;
      now[0][1] += cl[b[y]][0];
      now[1][1] += cl[b[y]][1];
    }
    int c = id < 0 ? id = -id, -1 : 1;
    ans[id - 1] += c * now[u][v];
    //  dbg("%d %d %d %d : %lld\n", l, r, u, v, now[u][v]);
  }
  for (auto x : ans)
    if (x != -1e9) std::cout << x << "\n";
  return 0;
}

P7897 [Ynoi2006] spxmcq

给定一颗 \(n\) 个节点有根树,第 \(i\) 节点权值为 \(a_i\)

在这个树上支持一种询问:给定节点 \(u\) 和参数 \(x\)假如 所有节点点权加 \(x\),在这种情况下,求对于所有完全在 \(u\) 子树内并包含 \(u\) 的连通点集,权值之和最大可能为多少?

\(1\le n,m\le 10^6\)\(|a_i|,|x|\le 10^8\)

显然有暴力dp:\(f_x = \sum \max(f_y, 0)\),显然随着 \(x\) 增加,\(f_x\) 是不降的,所以 \(f\) 变成大于0 只会有一个时间点,考虑在对应的时间点连上向父亲的那条边,然后维护一下子树大小。\(f_x = sum + x\cdot siz > 0\),则 \(x > -\frac{sum}{siz}\),用树状数组维护 \(sum, siz\),用堆维护一下 \(-\frac{sum}{siz}\) 即可。

void solve() {
  int n, m;
  cin >> n >> m;
  
  std::vector<int> a(n), fa(n);
  std::vector<std::vector<int>> g(n);
  
  for (int i = 1; i < n; i++) {
    cin >> fa[i];
    g[--fa[i]].push_back(i);
  }

  std::vector<int> in(n), out(n);
  int cnt = 0;

  std::function<void(int)> dfs = [&](int x) {
    in[x] = cnt++;
    for (int y : g[x]) {
      dfs(y);
    }
    out[x] = cnt;
  };
  
  dfs(0);

  std::priority_queue<PII, std::vector<PII>, std::greater<PII>> q;

  std::vector<int> f(n), siz(n, 1);
  std::vector<LL> sum(n);

  auto find = [&](int i) {
    while (i != f[i]) i = f[i] = f[f[i]];
    return i;
  };

  
  for (int i = 0; i < n; i++) {
    cin >> a[i];
    f[i] = i;
    sum[i] = a[i];
    q.push({-a[i], i});
  }

  std::vector<std::array<int, 3>> qry(m);

  for (int i = 0; i < m; i++) {
    int u, x;
    cin >> u >> x;
    qry[i] = {x, u - 1, i};
  }

  std::sort(qry.begin(), qry.end());

  Fenwick<LL> fsum(n);
  Fenwick<int> fsiz(n);

  std::vector<LL> ans(m);

  for (auto &[u, x, id] : qry) {
    while (!q.empty() && q.top().first <= u) {
      int x = q.top().second;
      q.pop();

      if (!x || x != f[x]) continue;
      
      int p = fa[x];
      fsum.add(in[p], sum[x]);
      fsiz.add(in[p], siz[x]);
      f[x] = p = find(p);
      sum[p] += sum[x], siz[p] += siz[x];
      if (p) {
        fsum.add(in[fa[p]], -sum[x]);
        fsiz.add(in[fa[p]], -siz[x]);
        LL u = sum[p], v = siz[p];
        q.push({u > 0 ? -(u / v) : (-u + v - 1) / v, p});
      }
    }
    
    ans[id] = (fsum.ask(in[x], out[x]) + a[x]) + 1LL * u * (fsiz.ask(in[x], out[x]) + 1);
  }
  
  for (auto x : ans) std::cout << x << "\n";
}

P5068 [Ynoi2015] 我回来了

亵渎的定义为:等概率随机在 \([L,R]\) 中选出一个整数作为伤害值 \(d\),对所有随从造成 \(d\) 点伤害,如果有随从死亡,则再次施放该法术,但伤害值不重新随机;如果没有随从死亡,则停止释放。 进行 \(m\) 次如下类型的操作: 1. 在场面上加入一个血量为 \(h\) 的随从,这里随从的血量都不能超过 \(n\); 2. 给定 \(L, R\),询问亵渎期望触发多少次;

\(1\le n\le 10^5, 1\le m\le 10^6\)

不难发现实际上是求 \(d = L \cdots R\) 的答案之和,然后 \(d\) 的答案是最小的 \(i\) 满足 \(((i-1) \cdot d, i \cdot d]\) 没有随从。

然后可以发现,答案之和是 \(n\log n\) 级别(调和级数),可以暴力维护,简单用线段树维护一下即可。

constexpr int N(1e5 + 5);

int n, m, ans[N];
LL c[N];
void add(int p, int x) {
  for (; p <= n; p += p & -p) c[p] += x;
}
LL ask(int p) {
  LL r = 0;
  for (; p; p -= p & -p) r += c[p];
  return r;
}
int k;
std::vector<int> s[N], q[N];
PII max[1 << 18];
void pushup(int o) {
  max[o] = std::max(max[o << 1], max[o << 1 | 1]);
}
PII ask(int l, int r) {
  PII res;
  for (l += k, r += k; l < r; l >>= 1, r >>= 1) {
    if (l & 1) smax(res, max[l++]);
    if (r & 1) smax(res, max[--r]);
  }
  return res;
}
int main() {
 // freopen("t.in", "r", stdin);
//  freopen(".out", "w", stdout);
  std::ios::sync_with_stdio(false);
  std::cin.tie(nullptr);
  std::cin >> n >> m;
  k = 1 << std::__lg(2 * n - 1);
  for (int i = 1; i <= n; i++) {
    for (int j = 0; j < n; j += i) {
      max[j + k] = {i + j, j};
      s[j].push_back(i + j);
    }
    q[i].resize((n - 1 + i) / i);
  }
  for (int i = k - 1; i; i--) pushup(i);
  while (m--) {
    int opt, x, y;
    std::cin >> opt >> x;
    if (opt == 1) {
      PII z;
      while ((z = ask(0, x)).first >= x) {
        y = z.second;
        while (!s[y].empty() && s[y].back() >= x) {
          int i = s[y].back() - y;
          q[i][y / i] = 1;
          int p = ans[i];
          while (ans[i] < q[i].size() && q[i][ans[i]]) ans[i]++;
          if (ans[i] - p) add(i, ans[i] - p);
          s[y].pop_back();
        }
        max[y + k] = {s[y].empty() ? 0 : s[y].back(), y};
        y += k;
        while (y > 1) pushup(y >>= 1);
      }
    } else {
      std::cin >> y;
      std::cout << ask(y) - ask(x - 1) + y - x + 1 << "\n";
    }
  }
  return 0;
}

P4688 [Ynoi2016] 掉进兔子洞

一个长为 \(n\) 的序列 \(a\)。有 \(m\) 个询问,每次询问三个区间,把三个区间中同时出现的数一个一个删掉,问最后三个区间剩下的数的个数和,询问独立。

\(1\leq n , m \leq 10^5\)\(1 \leq a_i\leq 10^9\)

就是求 \(|S_1\cap S_2 \cap S_3|\)

莫队算一下,用bitset来求交即可。

因为空间不够开的,所以把询问分段做就行了。

constexpr int N(1e5 + 5), T(311);
struct Qry {
  int l, r, id;
  int block() const { return l / T; }
  bool operator<(const Qry &rhs) const {
    int u = block(), v = rhs.block();
    return u == v ? u & 1 ? r < rhs.r : r > rhs.r : u < v;
  }
};
int main() {
  std::ios::sync_with_stdio(false);
  std::cin.tie(nullptr);
  int n, m;
  std::cin >> n >> m;
  std::vector<int> a(n), b;
  for (auto &x : a) std::cin >> x;
  b = a;
  std::sort(b.begin(), b.end());
  for (auto &x : a) x = std::lower_bound(b.begin(), b.end(), x) - b.begin();
  std::vector<Qry> q;
  std::vector<int> ans;
  std::vector<std::bitset<N>> p;
  auto solve = [&] {
    std::sort(q.begin(), q.end());
    int x = 0, y = -1;
    std::vector<int> c(n);
    std::bitset<N> now;
    now.reset();
    p.resize(ans.size());
    for (auto &v : p) v.set();
    auto add = [&](int x) { now[x + c[x]++] = 1; };
    auto del = [&](int x) { now[x + --c[x]] = 0; };
    for (auto [l, r, id] : q) {
      while (x > l) add(a[--x]);
      while (y < r) add(a[++y]);
      while (x < l) del(a[x++]);
      while (y > r) del(a[y--]);
      p[id] &= now;
    }
    for (int i = 0; i < ans.size(); i++) {
      std::cout << ans[i] - 3 * p[i].count() << "\n";
    }
  };
  for (int i = 0; i < m; i++) {
    int now = ans.size();
    ans.push_back(0);
    for (int j = 0, x, y; j < 3; j++) {
      std::cin >> x >> y;
      x--, y--;
      q.push_back({x, y, now});
      ans[now] += y - x + 1;
    }
    if (ans.size() == 33300 || i == m - 1) {
      solve();
      q.clear();
      ans.clear();
    }
  }
  return 0;
}

P5355 [Ynoi2017] 由乃的玉米田

给你一个序列 \(a\),长度为 \(n\),有 \(m\) 次操作,每次询问一个区间是否可以选出两个数它们的差为 \(x\),或者询问一个区间是否可以选出两个数它们的和为 \(x\),或者询问一个区间是否可以选出两个数它们的乘积为 \(x\) ,或者询问一个区间是否可以选出两个数它们的商为 \(x\)(没有余数) ,这四个操作分别为操作 \(1,2,3,4\)

选出的这两个数可以是同一个位置的数。

所有输入的数在 \([0,10^5]\) 内,序列中的元素在 \([1,10^5]\) 内。

莫队,加减都用 bitset 即可。乘的话,对 \(x\) 分解质因数即可。

除的话,如果 \(x\) 大于根号,暴力枚举即可。

如果 \(x\) 小于根号,对于每个 \(x\) 分别做一下。对于每个数,维护一下它左边第一个符合条件的,然后查询区间最大值,搞一个 \(O(1)\) - \(O(\sqrt N)\) 的数据结构。

constexpr int N(1e5 + 5), T(384);
std::vector<int> fac[N];
void init(int n) {
  for (int i = 1; i * i <= n; i++) {
    for (int j = i * i; j <= n; j += i) {
      fac[j].push_back(i);
    }
  }
}
std::bitset<N> now, rev;
int n, m, a[N];
struct Qry {
  int opt, l, r, x, id;
  bool operator<(const Qry &rhs) const {
    return l / T == rhs.l / T ? l / T & 1 ? r < rhs.r : r > rhs.r : l < rhs.l;
  } 
} q[N];
int cnt[N];
void add(int x) {
  if (1 == ++cnt[x]) now[x] = rev[N - x] = 1;
}
void del(int x) {
  if (0 == --cnt[x]) now[x] = rev[N - x] = 0;
}

int ans[N];
std::vector<int> divq[T];

struct MaxDS {
  int a[N], b[(N - 1 + T) / T];
  void clear() {
    memset(a, 0, sizeof a);
    memset(b, 0, sizeof b);
  }
  void update(int x, int y) {
    smax(a[x], y);
    smax(b[x / T], y);
  }
  int ask(int l, int r) {
    int res = 0, bl = l / T, br = r / T;
    if (bl < br) {
      for (int i = l; i < (bl + 1) * T; i++) smax(res, a[i]);
      for (int i = bl + 1; i < br; i++) smax(res, b[i]);
      for (int i = br * T; i <= r; i++) smax(res, a[i]);
    } else {
      for (int i = l; i <= r; i++) smax(res, a[i]);
    }
    
    return res;
  }
} maxDS;
int pos[N];
int main() {
  init(1e5);
  // freopen("t.in", "r", stdin);
  // freopen("t.out", "w", stdout);
  std::ios::sync_with_stdio(false);
  std::cin.tie(nullptr);
  std::cin >> n >> m;
  for (int i = 1; i <= n; i++) {
    std::cin >> a[i];
  }
  for (int i = 1; i <= m; i++) {
    std::cin >> q[i].opt >> q[i].l >> q[i].r >> q[i].x;
    q[i].id = i;    
  }
  std::sort(q + 1, q + 1 + m);

  for (int i = 1, x = 1, y = 0; i <= m; i++) {
    if (q[i].opt == 4 && q[i].x < T) {
      divq[q[i].x].push_back(i);
      continue;
    }
    while (x > q[i].l) add(a[--x]);
    while (y < q[i].r) add(a[++y]);
    while (x < q[i].l) del(a[x++]);
    while (y > q[i].r) del(a[y--]);
    if (q[i].opt == 1) {
      ans[q[i].id] = (now & now >> q[i].x).any();
    } else if (q[i].opt == 2) {
      ans[q[i].id] = (now & rev >> (N - q[i].x)).any();
    } else if (q[i].opt == 3) {
      for (int v : fac[q[i].x]) {
        if (now[v] && now[q[i].x / v]) {
          ans[q[i].id] = 1;
          break;
        }
      }
    } else {
      for (int v = 1; v * q[i].x <= 100000; v++) {
        if (now[v] && now[v * q[i].x]) {
          ans[q[i].id] = 1;
          break;
        }
      }
    }
  }
  for (int i = 1; i < T; i++) {
    if (divq[i].empty()) continue;
    maxDS.clear();
    memset(pos, 0, sizeof pos);
    for (int j = 1; j <= n; j++) {
      pos[a[j]] = j;
      if (a[j] % i == 0) maxDS.update(j, pos[a[j] / i]);
      if (1LL * a[j] * i <= 1e5) maxDS.update(j, pos[a[j] * i]);
    }
    for (int j : divq[i]) {
      ans[q[j].id] = maxDS.ask(q[j].l, q[j].r) >= q[j].l;
    }
  }
  for (int i = 1; i <= m; i++) {
    std::cout << (ans[i] ? "yuno\n" : "yumi\n");
  }
  return 0;
}

P5607 [Ynoi2013] 无力回天 NOI2017

给你一个长度为 n 的整数序列 \(a_1\), \(a_2\), \(\ldots\), \(a_n\) ,你需要实现以下两种操作,每个操作都可以用四个整数 \(opt\;l\;r\;v\) 来表示:

\(opt=1\) 时,代表把一个区间 \([l,r]\) 内的所有数都 xor 上 \(v\)

\(opt=2\) 时, 查询一个区间 \([l,r]\) 内选任意个数(包括 \(0\) 个)数 xor 起来,这个值与 \(v\) 的最大 xor 和是多少。

\(1 \le n , m \le 5 \times 10^4\),值域在 \([0,10^9]\) 之间

差分一下变成单点修改区间线性基,线段树维护一下。

using LBasis = std::array<int, 31>;
void ins(LBasis &p, int x) {
  if (!x) return;
  for (int i = 30; i >= 0; i--) {
    if (x >> i & 1 ^ 1) continue;
    if (!p[i]) {
      p[i] = x;
      break;
    }
    x ^= p[i];
  }
}
LBasis merge(LBasis a, LBasis b) {
  auto r = a;
  for (int i = 0; i <= 30; i++) ins(r, b[i]);
  return r;
}
constexpr int N(5e4 + 5);
#define ls o << 1
#define rs o << 1 | 1
LBasis p[N << 2];
int n, a[N], sum[N << 2];
void pushup(int o) {
  p[o] = merge(p[ls], p[rs]);
  sum[o] = sum[ls] ^ sum[rs];
}
void build(int o, int l, int r) {
  if (l == r) {
    p[o].fill(0);
    ins(p[o], sum[o] = a[l] ^ a[l - 1]);
    return;
  }
  int m = l + r >> 1;
  build(ls, l, m), build(rs, m + 1, r);
  pushup(o);
}
void ins(int o, int l, int r, int x, int y) {
  if (l == r) {
    p[o].fill(0);
    ins(p[o], sum[o] ^= y);
    return;
  }
  int m = l + r >> 1;
  x <= m ? ins(ls, l, m, x, y) : ins(rs, m + 1, r, x, y);
  pushup(o);
}
LBasis ask(int o, int l, int r, int x, int y) {
  if (x <= l && r <= y) return p[o];
  int m = l + r >> 1;
  if (y <= m) return ask(ls, l, m, x, y);
  if (x > m) return ask(rs, m + 1, r, x, y);
  return merge(ask(ls, l, m, x, y), ask(rs, m + 1, r, x, y));
}
int qsum(int o, int l, int r, int x, int y) {
  if (y < l || x > r) return 0;
  if (x <= l && r <= y) return sum[o];
  int m = l + r >> 1;
  return qsum(ls, l, m, x, y) ^ qsum(rs, m + 1, r, x, y);
}
int main() {
  std::ios::sync_with_stdio(false);
  std::cin.tie(nullptr);
  int n, m;
  std::cin >> n >> m;
  for (int i = 1; i <= n; i++) {
    std::cin >> a[i];
  }
  build(1, 1, n);
  while (m--) {
    int opt, l, r, v;
    std::cin >> opt >> l >> r >> v;
    if (opt == 1) {
      ins(1, 1, n, l, v);
      if (r < n) ins(1, 1, n, r + 1, v);
    } else {
      LBasis p;
      if (l < r) 
        p = ask(1, 1, n, l + 1, r);
      else
        p.fill(0);
      int t;
      ins(p, t = qsum(1, 1, n, 1, l));
      for (int i = 30; i >= 0; i--) smax(v, v ^ p[i]);
      std::cout << v << "\n";
    }
  }
  return 0;
}

P5356 [Ynoi2017] 由乃打扑克

给你一个长为 \(n\) 的序列 \(a\),需要支持 \(m\) 次操作,操作有两种:

  1. 查询区间 \([l,r]\) 的第 \(k\) 小值。
  2. 区间 \([l,r]\) 加上 \(k\)

\(1\leq n,m\leq 10^5\)\(-2\times 10^4\leq\) 每次加上的数和原序列的数 \(\leq 2\times 10^4\)

线段树,小于 \(B\) 的点才 pushup。

constexpr int N(1e5 + 5), T(512);
#define ls o << 1
#define rs o << 1 | 1

std::vector<int> g[N << 2];
int add[N << 2];
void pushup(int o) {
  g[o].resize(g[ls].size() + g[rs].size());
  for (int i = 0, j = 0, k = 0; i < g[ls].size() || j < g[rs].size(); k++) {
    if (j == g[rs].size() || i < g[ls].size() && g[ls][i] + add[ls] < g[rs][j] + add[rs]) {
      g[o][k] = g[ls][i++] + add[ls];
    } else {
      g[o][k] = g[rs][j++] + add[rs];
    }
  }
}

void update(int o, int l, int r, int x, int y, int z) {
  if (x <= l && r <= y) {
    add[o] += z;
    return;
  }
  int m = l + r >> 1;
  if (x <= m) update(ls, l, m, x, y, z);
  if (y > m) update(rs, m + 1, r, x, y, z);
  if (r - l + 1 <= T) pushup(o);
}

std::vector<PII> id;
void get(int o, int l, int r, int x, int y, int z) {
  z += add[o];
  if (x <= l && r <= y && r - l + 1 <= T) {
    id.push_back({o, z});
    return;
  }
  int m = l + r >> 1;
  if (x <= m) get(ls, l, m, x, y, z);
  if (y > m) get(rs, m + 1, r, x, y, z);
}

int a[N];
void build(int o, int l, int r) {
  if (l == r) {
    g[o] = {a[l]};
    return;
  }
  int m = l + r >> 1;
  build(ls, l, m), build(rs, m + 1, r);
  if (r - l + 1 <= T) pushup(o);
}

int ask(int k) {
  int l = INT_MAX, r = INT_MIN;
  for (auto &[o, z] : id) {
    smin(l, g[o].front() + z);
    smax(r, g[o].back() + z);
  }
  
  while (l < r) {
    int m = LL(l) + r + 1 >> 1, c = 0;
    for (auto &[o, z] : id) {
      c += std::lower_bound(g[o].begin(), g[o].end(), m - z) - g[o].begin();
      if (c >= k) break; 
    }

    if (c >= k) {
      r = m - 1;
    } else {
      l = m;
    }
  }

  return l;
}

void solve() {
  int n, m;
  std::cin >> n >> m;
  for (int i = 1; i <= n; i++) {
    std::cin >> a[i];
  }
  build(1, 1, n);

  while (m--) {
    int opt, l, r, x;
    std::cin >> opt >> l >> r >> x;
    if (opt == 1) {
      if (x < 1 || r - l + 1 < x) {
        std::cout << "-1\n";
        continue;
      }
      
      id.clear();
      get(1, 1, n, l, r, 0);
      std::cout << ask(x) << "\n";
    } else {
      update(1, 1, n, l, r, x);
    }
  }
}

P5309 [Ynoi2011] 初始化

Mayuri 有 \(n\) 颗星星,每颗星星都有一个明亮度 \(A_{i}\) 。Mayuri 时常想知道一个区间 \([l,r]\) 内所有星星的明亮度的总和是多少。但是星星是会眨眼的,所以星星的明亮度是会变化的。有的时候,下标为 \(y,y+x,y+2x,y+3x,\ldots,y+kx\) 的星星的明亮度会增加 \(z\)。保证 \(y\leq x\)

答案要对 \(10^{9}+7\) 取模。

\(1\leq n,m\leq 2\times 10^5\)\(1\leq y\leq x\leq n\)\(1\leq l\leq r\leq n\)\(0\leq A_i,z \leq 10^{9}+7\)

简单根号分治一下即可。

constexpr int N(2e5 + 5), B(256);

struct SumDS {
  Z s0[N], s1[N];
  void add(int p, const Z &x) {
    s0[p] += x;
    s1[p / B] += x; 
  }
  Z ask(int l, int r) {
    Z ans = 0;
    if (r - l > B * 2) {
      while (l % B) ans += s0[l++];
      while (l + B <= r) ans += s1[l / B], l += B;
    }
    while (l < r) ans += s0[l++];
    return ans;
  }
} sumDS;
int n, m;
Z a[B][B + 1];

Z ask(int p) {
  Z ans = 0;
  for (int i = 1; i < B; i++) {
    int k = p % i, v = p / i;
    ans += a[i][i] * v + a[i][k];
  }
  return ans;
}

Z ask(int l, int r) {
  return ask(r) - ask(l);
}

void solve() {
  cin >> n >> m;
  for (int i = 0; i < n; i++) {
    int x;
    cin >> x;
    sumDS.add(i, x);
  }
  
  while (m--) {
    int opt;
    cin >> opt;
    if (opt == 1) {
      int x, y, _;
      cin >> x >> y >> _;
      Z z = _;

      y--;
      if (x >= B) {
        for (int i = y; i < n; i += x) {
          sumDS.add(i, z);
        }
      } else {
        for (int i = y + 1; i <= x; i++) {
          a[x][i] += z;
        }
      }
    } else {
      int l, r;
      cin >> l >> r;
      l--;
      Z ans = sumDS.ask(l, r) + ask(l, r);
      cout << ans.x << "\n";
    }
  }
}

P5071 [Ynoi2015] 此时此刻的光辉

长为 \(n\) 的序列,有 \(m\) 次查询,每次查询一段区间的乘积的约数个数 \(\bmod 19260817\) 的值。

\(1\leq n,m\leq 10^5\)\(1 \leq a_i \leq10^9\)

rho + 莫队,先要把小的质因子除掉。

constexpr int N(1e5 + 5), T(256);

struct Qry {
  int l, r, id;
  bool operator<(const Qry &rhs) const {
    return l / T == rhs.l / T ? l / T & 1 ? r < rhs.r : r > rhs.r : l < rhs.l;
  }
} q[N];
int ans[N];
std::vector<PII> fac[N];
Z inv[N * 30];
void init() {
  inv[1] = 1;
  for (int i = 2; i <= 3000000; i++) {
    inv[i] = (P - P / i) * inv[P % i];
  }
}
int b[N * 30], t;
int main() {
  std::ios::sync_with_stdio(false);
  std::cin.tie(nullptr);
  int n, m;
  std::cin >> n >> m;
  sieve();
  for (int i = 1, x; i <= n; i++) {
    std::cin >> x;
    for (auto j : primes) {
      int c = 0;
      while (x % j == 0) x /= j, c++;
      if (c) fac[i].push_back({j, c});
    }
    auto f = factor(x);
    std::sort(f.begin(), f.end());
    for (int j = 0, k; j < f.size(); j = k) {
      for (k = j + 1; k < f.size() && f[j] == f[k]; k++) ;
      fac[i].push_back({f[j], k - j});
    }
    for (auto &[x, y] : fac[i]) {
      b[t++] = x;
    }
  }
  
  std::sort(b, b + t);
  t = std::unique(b, b + t) - b;
  for (int i = 1; i <= n; i++) {
    for (auto &[x, y] : fac[i]) {
      x = std::lower_bound(b, b + t, x) - b;
    }
  }
  memset(b, 0, sizeof b);
  for (int i = 1; i <= m; i++) {
    std::cin >> q[i].l >> q[i].r;
    q[i].id = i;
  }
  std::sort(q + 1, q + 1 + m);
  Z now = 1;
  init();
  auto add = [&](int i, int z) {
    for (auto &[x, y] : fac[i]) {
      now *= inv[b[x] + 1];
      b[x] += z * y;
      now *= b[x] + 1;
    }
  };
  for (int i = 1, x = 1, y = 0; i <= m; i++) {
    while (y < q[i].r) add(++y, 1);
    while (x > q[i].l) add(--x, 1);
    while (x < q[i].l) add(x++, -1);
    while (y > q[i].r) add(y--, -1);
    ans[q[i].id] = now.x;
  }
  for (int i = 1; i <= m; i++) {
    std::cout << ans[i] << "\n";
  }
  return 0;
}

P5610 [Ynoi2013] 大学

一个长为 \(n\)非负整数序列 \(a\),支持以下两个操作:

  • 1 l r x:把区间 \([l,r]\) 中所有 \(x\) 的倍数除以 \(x\)
  • 2 l r:查询区间 \([l,r]\) 的和。

本题强制在线,每次的 \(l,r,x\) 需要 xor 上上次答案,如果之前没有询问,则上次答案为 \(0\)

\(1\leq n,m\leq 10^5\)\(0\leq a_i\leq 5\times 10^5\)

\(n\leq\) \(10^1\) \(10^2\) \(10^3\) \(10^4\) \(10^5\) \(10^6\) \(10^7\) \(10^8\) \(10^9\)
\(\max\{\omega(n)\}\) 2 3 4 5 6 7 8 8 9
\(\max\{\operatorname{d}(n)\}\) 4 12 32 64 128 240 448 768 1344
\(n\leq\) \(10^{10}\) \(10^{11}\) \(10^{12}\) \(10^{13}\) \(10^{14}\) \(10^{15}\) \(10^{16}\) \(10^{17}\) \(10^{18}\)
\(\max\{\omega(n)\}\) 10 10 11 12 12 13 13 14 15
\(\max\{\operatorname{d}(n)\}\) 2304 4032 6720 10752 17280 26880 41472 64512 103680

约数个数是200个级别,把每个数以及所有约数搞到一个有序数组里面,数组按照数字大小为第一关键字,数字出现位置为第二关键字排序。

每个数最多除 \(\log\) 次。如果一个数不整除 \(x\),就用并查集把他跳了。

用树状数组维护区间和。

constexpr int N(1e5 + 5), M(5e5 + 5);
int n, q, m, a[N], b[M], c[M], *fac, *pos, *f;

int find(int x) {
  while (f[x] != x) x = f[x] = f[f[x]];
  return x;
}

int get(int x, int p) {
  int l = c[x], r = c[x + 1];
  while (l < r) {
    int m = l + r >> 1;
    if (pos[m] < p) {
      l = m + 1;
    } else {
      r = m;
    }
  }
  return l;
}

void solve() {
  cin >> n >> q;

  for (int i = 1; i <= n; i++) {
    cin >> a[i];
    c[a[i]]++;
    smax(m, a[i]);
  }
  c[0] = c[1] = 0;
  
  for (int i = 2; i <= m; i++) {
    for (int j = i; j <= m; j += i) {
      b[j]++;
    }
  }

  for (int i = 1; i <= m + 1; i++) b[i] += b[i - 1];

  fac = new int[b[m]];
  
  for (int i = 2; i <= m; i++) {
    for (int j = i; j <= m; j += i) {
      fac[--b[j]] = i;
    }
  }
  
  for (int i = 2; i <= m; i++) {
    for (int j = i + i; j <= m; j += i) {
      c[i] += c[j];
    }
  }

  for (int i = 1; i <= m + 1; i++) c[i] += c[i - 1];

  int tot = c[m];
  pos = new int[tot];
  f = new int[tot];
  
  for (int i = n; i; i--) {
    if (a[i] <= 1) continue;
    for (int j = b[a[i]]; j < b[a[i] + 1]; j++) {
      int x = fac[j];
      pos[--c[x]] = i;
    }
  }

  std::iota(f, f + tot, 0);

  Fenwick<LL> fen(n);
  for (int i = 1; i <= n; i++) {
    fen.add(i - 1, a[i]);
  }
  
  LL ans = 0;
  while (q--) {
    int opt, l, r;

    auto read = [&]() -> int {
      LL x;
      cin >> x;
      x ^= ans;
      return x;
    };

    cin >> opt;
    l = read(), r = read();
    
    if (opt == 1) {
      int x = read();
      if (x <= 1) continue;
      l = get(x, l), r = get(x, r + 1);
      for (; l < r; l++) {
        l = find(l);
        if (l >= r) break;
        int i = pos[l];
        if (a[i] % x) {
          f[l] = l + 1;
          continue;
        }
        int v = a[i] / x;
        fen.add(i - 1, v - a[i]);
        a[i] = v;
      }
    } else {
      ans = fen.ask(r) - fen.ask(l - 1);
      cout << ans << "\n";
    }
  }
}

P5311 [Ynoi2011] 成都七中

给你一棵 \(n\) 个节点的树,每个节点有一种颜色,有 \(m\) 次查询操作。

查询操作给定参数 \(l\ r\ x\),需输出:

将树中编号在 \([l,r]\) 内的所有节点保留,\(x\) 所在连通块中颜色种类数。

每次查询操作独立。

对于 \(100\%\) 的数据,所有出现过的数在 \([1,10^5]\)

\(x\) 所在联通块,在点分树上的根是可以确定的:点分治的时候,\(x\) 到根的路径上的点全部都在 \([l, r]\) 内。

把询问挂到这个根上,每个根分别处理,如果一个点到根的路径上的点都在 \([l, r]\) 内,那么这个点合法。

每个点可以视为一个二元组 \((min, max)\) 表示到根路径上的最值,按照 \(max\) 排序,维护每个颜色的 \(min\) 的最大值,然后只需要查询 \(min \geq l\) 的数量即可。

constexpr int N(1e5 + 5);
int n, m, a[N], g[N * 2], deg[N];
std::array<int, 3> q[N];
int cqx[N], qx[N];
bool vq[N], vis[N];

int siz[N];
void getSize(int x, int p) {
  siz[x] = 1;
  for (int i = deg[x]; i < deg[x + 1]; i++) {
    if (g[i] != p && !vis[g[i]]) {
      getSize(g[i], x);
      siz[x] += siz[g[i]];
    }
  }
}
int getRoot(int x, int p, int s) {
  for (int i = deg[x]; i < deg[x + 1]; i++) {
    if (g[i] != p && !vis[g[i]] && siz[g[i]] * 2 > s) {
      return getRoot(g[i], x, s);
    }
  }
  return x;
}

int id[N], in[N], cur;
std::array<int, 3> seq[N * 20];
void dfs(int x, int p) {
  auto &[l, r, c] = seq[cur - 1];
  for (int i = cqx[x]; i < cqx[x + 1]; i++) {
    int j = qx[i];
    if (!vq[j] && q[j][0] <= l && r <= q[j][1]) {
      vq[j] = true;
      q[j][2] = id[0];
    }
  }
  for (int i = deg[x]; i < deg[x + 1]; i++) {
    int y = g[i];
    if (y != p && !vis[y]) {
      seq[cur++] = {std::min(l, y), std::max(r, y), a[y]};
      dfs(y, x);
    }
  }
}
void solve(int x) {
  getSize(x, 0);
  x = getRoot(x, 0, siz[x]);
  int o = ++id[0];
  id[o] = x;
  seq[cur++] = {x, x, a[x]};
  dfs(x, 0);
  in[o + 1] = cur;

  std::sort(seq + in[o], seq + in[o + 1], std::greater());

  vis[x] = true;
  for (int i = deg[x]; i < deg[x + 1]; i++) {
    if (!vis[g[i]]) solve(g[i]);
  }
}

int ans[N], min[N], c[N];
void add(int p, int x) {
  for (; p <= n; p += p & -p) c[p] += x;
}
int ask(int p) {
  int r = 0;
  for (; p; p -= p & -p) r += c[p];
  return r;
}

void solve() {
  std::cin >> n >> m;
  for (int i = 1; i <= n; i++) {
    std::cin >> a[i];
  }

  std::vector<PII> e(n - 1);
  for (auto &[x, y] : e) {
    std::cin >> x >> y;
    deg[x]++, deg[y]++;
  }
  for (int i = 1; i <= n + 1; i++) deg[i] += deg[i - 1];
  for (auto &[x, y] : e) {
    g[--deg[x]] = y, g[--deg[y]] = x;
  }

  for (int i = 1; i <= m; i++) {
    std::cin >> q[i][0] >> q[i][1] >> q[i][2];
    cqx[q[i][2]]++;
  }
  for (int i = 1; i <= n + 1; i++) {
    cqx[i] += cqx[i - 1];
  }
  for (int i = 1; i <= m; i++) {
    qx[--cqx[q[i][2]]] = i;
  }

  solve(1);
  
  std::vector<int> p(m);
  std::iota(p.begin(), p.end(), 1);
  std::sort(p.begin(), p.end(), [&](int i, int j) {
    return q[i][0] < q[j][0];
  });

  memset(cqx, 0, sizeof cqx);
  for (int i = 1; i <= m; i++) {
    cqx[q[i][2]]++;
  }
  for (int i = 1; i <= n + 1; i++) {
    cqx[i] += cqx[i - 1];
  }
  for (int i : p) qx[--cqx[q[i][2]]] = i;

  for (int i = 1; i <= 1e5; i++) min[i] = n + 1;
  for (int i = 1; i <= n; i++) {
    int k = in[i];
    for (int j = cqx[i]; j < cqx[i + 1]; j++) {
      int id = qx[j];
      while (k < in[i + 1] && seq[k][0] >= q[id][0]) {
        auto &[l, r, c] = seq[k++];
        if (r < min[c]) {
          add(min[c], -1);
          add(min[c] = r, 1);
        }
      }
      ans[id] = ask(q[id][1]);
    }
    while (k > in[i]) {
      auto &[l, r, c] = seq[--k];
      if (min[c] != n + 1) {
        add(min[c], -1);
        min[c] = n + 1;
      }
    }
  }

  for (int i = 1; i <= m; i++) {
    std::cout << ans[i] << "\n";
  }
}

P3934 [Ynoi2016] 炸脖龙 I

给一个长为\(n\)的序列,\(m\)次操作,每次操作:

1、区间\([l,r]\)\(x\)

2、对于区间\([l,r]\),查询:

\(a[l]^{a[l+1]^{a[l+2]^{\dots ^{a[r]}}}} \mod p\)

对于100%的数据,\(n , m \le 500000\) , 序列中每个数在\([1,2\cdot 10^9]\)内,\(p \le 2 \cdot 10^7\), 每次加上的数在\([0,2\cdot 10^9]\)

拓展欧拉定理板子题,对 \(p\)\(phi\) 每两次就至少减半。

constexpr int N(5e5 + 5), M(2e7 + 5);
int np[M], pr[M / 10], pn, phi[M];
void sieve() {
  phi[1] = 1;
  const int n = 2e7;
  for (int i = 2; i <= n; i++) {
    if (!np[i]) pr[++pn] = i, np[i] = pn, phi[i] = i - 1;
    for (int j = 1; j <= pn && pr[j] * i <= n; j++) {
      np[i * pr[j]] = j;
      if (np[i] == j) {
        phi[i * pr[j]] = phi[i] * pr[j];
        break;
      }
      phi[i * pr[j]] = phi[i] * (pr[j] - 1);
    }
  }
}

int n, m;
LL c[N], d[N];
void add(int p, LL x) {
  d[p] += x;
  for (; p <= n; p += p & -p) c[p] += x;
}
LL ask(int p) {
  LL r = 0;
  for (; p; p -= p & -p) r += c[p];
  return r;
}

int cal(int l, int r, int p, LL x) {
  if (l == r || p == 1) return x >= p ? x % p + p : x;
  int k = cal(l + 1, r, phi[p], x + d[l + 1]), ans = 1;
  Barrett b(p);
  auto mul = [&](int x, int y) -> int {
    auto z = 1LL * x * y;
    return z >= p ? b.rem(z) + p : z;
  };
  if (x >= p) x = b.rem(x) + p;
  while (k) {
    if (k & 1) ans = mul(ans, x);
    if (k >>= 1) x = mul(x, x);
  }
  return ans;
}
void solve() {
  sieve();
  cin >> n >> m;

  for (int i = 1, d = 0; i <= n; i++) {
    int x;
    cin >> x;
    add(i, x - d);
    d = x;
  }
 
  while (m--) {
    int opt, l, r, x;
    cin >> opt >> l >> r >> x;
    if (opt == 1) {
      add(l, x), add(r + 1, -x);
    } else {
      cout << cal(l, r, x, ask(l)) % x << '\n';
    }
  }
}

P6749 『MdOI R3』Yoshino

Yoshino 给了你一个长度为 \(n\) 的序列,第 \(i\) 项为 \(a_i\)

现在 Yoshino 会对数列进行 \(m\) 次操作。

操作分成两种:

  • \(1\ l\ r\ x\) Yoshino 把数列下标在 \([l,r]\) 区间内的数修改为了一个从 \(x\) 开始公差为 \(1\) 的等差数列。

  • \(2\) Yoshino 需要查询整个数列中的逆序对个数。逆序对的定义为数对 \((i,j)\) 满足 \(i<j\)\(a_i>a_j\)

\(1\le n,m,a_i\le 3\times 10^4\)\(1\le l\le r\le n\)\(1\le x\le 3\times 10^4-r+l\)

颜色段均摊之后,把一段的信息记在一个端点上,然后变成查询左右比它大/小的,用cdq分治解决。

做这个题的时候,我开始把他搞成对角线修改查询,这个玩意很毒瘤,详见ptz22-s-qingyu-round,基本上不是人写的东西。但是你可以发现可以当作所有的 \(x, x+1, \dots, x+r-l\) 全部堆积在 \(l\) 上面,你在颜色分裂的时候再把一部分从前一个 \(l\) 搬到另一个位置,这样的话,就从对角线修改变成竖直线修改了,这个就是傻逼了。

constexpr int N(3e4 + 5), M(3e4 + 1), O(N * 20);
int n, m, a[N];
LL ans[N];

LL c[N][3];
void add(int p, int x) {
  LL v[3] = {(p - 1LL) * p * x, (1LL - 2 * p) * x, x};
  for (; p < M; p += p & -p) {
    c[p][0] += v[0], c[p][1] += v[1], c[p][2] += v[2];
  }
}
LL ask(int p) {
  LL v[3] = {};
  for (int i = p; i; i -= i & -i) {
    v[0] += c[i][0], v[1] += c[i][1], v[2] += c[i][2];
  }
  return (v[2] * p + v[1]) * p + v[0];
}

struct Opt {
  int x, l, r, k, id;
} t1[O], t2[O], t[O];
void cal(Opt *a, int l, int r) {
  if (l == r) return;
  int m = l + r >> 1;
  cal(a, l, m), cal(a, m + 1, r);
  for (int i = l, j = m + 1, k = l; k <= r; k++) {
    if (j > r || i <= m && a[i].x > a[j].x) {
      t[k] = a[i++];
      add(t[k].l, t[k].k), add(t[k].r + 1, -t[k].k);
    } else {
      t[k] = a[j++];
      if (t[k].id != -1) ans[t[k].id] += t[k].k * (ask(t[k].r) - ask(t[k].l - 1));
    }
  }
  for (int i = l; i <= m; i++) {
    add(a[i].l, -a[i].k), add(a[i].r + 1, a[i].k);
  }
  for (int i = l; i <= r; i++) a[i] = t[i];
}

void solve() {
  std::cin >> n >> m;
  for (int i = 1; i <= n; i++) {
    std::cin >> a[i];
  }

  std::set<int> s;

  for (int i = 1; i <= n + 1; i++) s.insert(i);

  int qc = 0, cnt = 0;

  auto add = [&](int l, int r, int k) {
    int x = a[l], y = x + r - l;
    t1[++cnt] = {l, x, y, k, qc};
    t2[cnt] = {-l, M - y, M - x, k, qc};
  };

  auto split = [&](int x) {
    auto it = --s.upper_bound(x);
    if (x == *it) return it;
    a[x] = a[*it] + x - *it;
    int l = *it, r = *++it - 1, u = a[x], v = u + r - x;
    t1[++cnt] = {l, u, v, -1, -1};
    t2[cnt] = {-l, M - v, M - u, -1, -1};
    t1[++cnt] = {x, u, v, 1, -1};
    t2[cnt] = {-x, M - v, M - u, 1, -1};
    return s.insert(x).first;
  };

  for (int i = 1; i <= n; i++) {
    add(i, i, 1);
  }
  
  while (m--) {
    int opt, l, r, x;
    std::cin >> opt;
    if (opt == 1) {
      std::cin >> l >> r >> x;
      auto lt = split(l), rt = split(r + 1);
      for (auto it = lt; it != rt; it = s.erase(it)) {
        add(*it, *std::next(it) - 1, -1);
      }
      s.insert(l), a[l] = x;
      add(l, r, 1);
    } else {
      qc++;
    }
  }

  // std::cerr << "???\n";

  cal(t1, 1, cnt);
  cal(t2, 1, cnt);
  
  for (int i = 0; i < qc; i++) {
    ans[i + 1] += ans[i];
    std::cout << ans[i] / 2 << "\n";
  }
}

P7811 [JRKSJ R2] 你的名字。

给你一个长为 \(n\) 的序列 \(a\),有 \(m\) 次查询,每次查询区间 \([l,r]\)\(k\) 意义下的最小值。

\(1\le n,m\le3\times10^5\)\(1\le a_i,k\le 10^5\)

先序列分块,把边角暴力掉,询问区间小的也暴力掉,然后变成在整块的区间上面查询。

根号分治,如果 \(k < B\),对每个模 \(k\) 后的数组预处理一下,随便查。

如果 \(k \geq B\),转换成区间 \(\geq ik\) 的最小值,扫描线之后,搞一个 \(O(\sqrt n)\) 单点修改,\(O(1)\) 查询区间最小值的结构。

这个结构怎么搞,搞一个 st 表,长度为 \(n\) 的st表单点修改的代价是 \(1 + 2 + 4 + 8 + \dots + n = O(n)\)

constexpr int N(3e5 + 5), V(1e5), B(444), S(N / B + 5);
int n, m, tot, a[N], b[N], c[N], min[S], st[S][10], lg[S], bel[N];

void build() {
  for (int i = tot - 1; i >= 0; i--) {
    for (int j = 1; i + (1 << j) <= tot; j++) {
      st[i][j] = std::min(st[i][j - 1], st[i + (1 << j - 1)][j - 1]);
    }
  }
}
void add(int p, int x) {
  p = bel[p];
  for (int i = p; i >= 0; i--) {
    for (int j = lg[p - i] + 1; i + (1 << j) <= tot; j++) st[i][j] = x;
  }
}
int ask(int u, int v) {
  int w = lg[v - u];
  return std::min(st[u][w], st[v - (1 << w)][w]);
}

struct Qry {
  int l, r, k, id;
  bool operator<(const Qry &rhs) const {
    return k < rhs.k;
  }
} q[N];

std::vector<int> fac[V + 5];
void solve() {
  cin >> n >> m;
  tot = (n - 1) / B + 1;
  lg[0] = -1;
  for (int i = 1; i <= tot; i++) lg[i] = lg[i >> 1] + 1;
  for (int i = 0; i <= n; i++) bel[i] = i / B;

  for (int i = 0; i < n; i++) {
    cin >> a[i];
  }

  std::vector<int> out(m, 1e9);

  {
    std::vector<Qry> t;
    t.reserve(m);
    for (int i = 0; i < m; i++) {
      int l, r, k;
      cin >> l >> r >> k;
      l--;

      Barrett bar(k);
      if (r - l <= B * 2) {
        for (int j = l; j < r; j++) {
          smin(out[i], bar.rem(a[j]));
        }
      } else {
        for (int j = l, z = bel[l] * B + B; j < z; j++) smin(out[i], bar.rem(a[j]));
        for (int j = bel[r] * B; j < r; j++) smin(out[i], bar.rem(a[j]));
        t.push_back({bel[l] + 1, bel[r], k, i});
        c[k]++;
      }
    }
    for (int i = 1; i <= V; i++) {
      c[i + 1] += c[i];
    }
    for (auto &v : t) q[--c[v.k]] = v;
    m = t.size();
  }

  std::vector<int> ans(m, 1e9);
  for (int i = 0; i < c[B]; i++) {
    int k = q[i].k;
    if (!i || k != q[i - 1].k) {
      for (int j = 0; j < tot; j++) st[j][0] = 1e9;
      Barrett bar(k);
      for (int j = 0; j < n; j++) {
        smin(st[bel[j]][0], bar.rem(a[j]));
      }
      build();
    }
    smin(ans[i], ask(q[i].l, q[i].r));
  }

  for (int i = B; i <= V; i++) {
    fac[0].push_back(i);
    for (int j = i; j <= V; j += i) {
      fac[j].push_back(i);
    }
  }

  std::vector<int> p(n);
  std::iota(p.begin(), p.end(), 0);
  std::sort(p.begin(), p.end(), [&](int i, int j) { return a[i] > a[j]; });

  memset(st, 0x3f, sizeof st);

  for (int i = V, j = 0; i >= 0; i--) {
    while (j < n && a[p[j]] >= i) {
      add(p[j], a[p[j]]), j++;
    }
    for (int x : fac[i]) {
      for (int k = c[x]; k < c[x + 1]; k++)
        smin(ans[k], ask(q[k].l, q[k].r) - i);
    }
  }

  for (int i = 0; i < m; i++) smin(out[q[i].id], ans[i]);
  for (int x : out) cout << x << '\n';
}

P6779 [Ynoi2009] rla1rmdq

给定一棵 \(n\) 个节点的树,树有边权,与一个长为 \(n\) 的序列 \(a\)

定义节点 \(x\) 的父亲为 \(fa(x)\),根 \(rt\) 满足 \(fa(rt)=rt\)

定义节点 \(x\) 的深度 \(dep(x)\) 为其到根简单路径上所有边权和。

\(m\) 次操作:

1 l r:对于 \(l \le i \le r\)\(a_i := fa(a_i)\)

2 l r:查询对于 \(l \le i \le r\),最小的 \(dep(a_i)\)

对于 \(100\%\) 的数据,\(1\le n,m\le 2\cdot 10^5\)\(1\le a_i\le n\),边权在 \([0,10^9]\) 之间。

分块之后,离线对每个块单独处理。

维护可能成为最小值的点的集合 \(S\),想象一下根和这些点的虚树,每次跳的话就是把叶子往上拨,记一个 \(vis\) 表示一个点是否被访问过,每次块整体跳的时候,直接暴力把集合中的点往上跳,如果发现已经访问过了,就删掉这个点,这样的话,只会跳 \(O(n)\) 次。

问题是有单独跳的,记录整块跳的次数 \(cnt\),还有某个点作为 \(S\) 中元素跟着往上跳的次数 \(c_x\),那么你单独跳的时候只要把缺的次数补上就行了,这样需要查询 k 级祖先,由于有 \(O(n\sqrt n)\) 次这样查询,看似要长链剖分搞一个 \(O(1)\) 查询的操作。

但是实际上只要HLD就行了,因为每个点到根只有 \(O(\log n)\) 条重链,重链总数是 \(O(n\log n)\) 的,所以复杂度没啥影响。

constexpr int N(2e5 + 5);
int n, m, r, dep[N], fa[N], in[N], id[N], dfn, siz[N], son[N], top[N];
std::vector<PII> g[N];
LL d[N];

void dfs1(int x) {
  siz[x] = 1;
  for (auto [y, z] : g[x]) {
    if (y == fa[x]) continue;
    fa[y] = x;
    d[y] = d[x] + z;
    dep[y] = dep[x] + 1;
    dfs1(y);
    siz[x] += siz[y];
    if (siz[y] > siz[son[x]]) son[x] = y;
  }
}
void dfs2(int x) {
  in[x] = ++dfn, id[in[x]] = x;
  if (!son[x]) return;
  top[son[x]] = top[x], dfs2(son[x]);
  for (auto [y, z] : g[x]) {
    if (y == son[x] || y == fa[x]) continue;
    top[y] = y, dfs2(y);
  }
}

int jmp(int x, int k) {
  if (dep[x] <= k) return r;
  for (;;) {
    int d = in[x] - in[top[x]];
    if (k <= d) return id[in[x] - k];
    k -= d + 1;
    x = fa[top[x]];
  }
}

int a[N], b[N], vis[N];
bool inp[N];
void solve() {
  std::cin >> n >> m >> r;
  for (int i = 1, x, y, z; i < n; i++) {
    std::cin >> x >> y >> z;
    g[x].push_back({y, z});
    g[y].push_back({x, z});
  }

  fa[r] = top[r] = r, dfs1(r), dfs2(r);

  for (int i = 1; i <= n; i++) std::cin >> a[i];

  std::vector<std::array<int, 3>> q(m);
  for (auto &[x, y, z] : q) std::cin >> x >> y >> z;

  std::vector<LL> res(m, 1e18);
  for (int l = 1, r, B = sqrt(n), t; l <= n; l = r + 1) {
    r = std::min(n, l + B);
    std::vector<int> p(r - l + 1);

    LL ans = 1e18;
    t = 0;
    for (int i = l; i <= r; i++) {
      if (vis[a[i]] != l) {
        vis[a[i]] = l;
        p[t++] = i;
        inp[i] = true;

        smin(ans, d[a[i]]);
      }
    }
    p.resize(t);

    int cnt = 0;
    for (int z = 0; z < m; z++) {
      auto [opt, x, y] = q[z];
      smax(x, l), smin(y, r);
      if (x > y) continue;
      if (x == l && y == r) {
        if (opt == 1) {
          cnt++;
          t = 0;
          for (int x : p) {
            a[x] = fa[a[x]], b[x]++;
            if (vis[a[x]] != l) {
              vis[a[x]] = l;
              p[t++] = x;
              smin(ans, d[a[x]]);
            } else {
              inp[x] = false;
            }
          }
          p.resize(t);
        } else {
          smin(res[z], ans);
        }
      } else {
        if (opt == 1) {
          for (int i = x; i <= y; i++) {
            a[i] = jmp(a[i], 1 + cnt - b[i]);
            b[i] = cnt;
            if (vis[a[i]] != l) {
              vis[a[i]] = l;
              smin(ans, d[a[i]]);
              if (!inp[i]) {
                inp[i] = true;
                p.push_back(i);
              }
            }
          }
        } else {
          for (int i = x; i <= y; i++) {
            a[i] = jmp(a[i], cnt - b[i]);
            b[i] = cnt;
            smin(res[z], d[a[i]]);
          }
        }
      }
    }
  }

  for (int i = 0; i < m; i++) {
    if (q[i][0] == 2) {
      std::cout << res[i] << "\n";
    }
  }
}

P6782 [Ynoi2008] rplexq

给定一棵 \(n\) 个节点的有根树,第 \(i\) 个点的编号是 \(i\)

\(m\) 次询问,每次询问给出 \(l,r,x\),求有多少点编号的二元组 \((i,j)\) 满足 \(l \le i < j \le r\)\(i\)\(j\) 的最近公共祖先是节点 \(x\)

\(1\le n,m\le 2\cdot 10^5\)\(1 \le l,r,x \le n\)

这个答案主要可以表示成 \(cnt_x^2 - \sum_{y} cnt_y^2\),考虑后者的计算。

对于每个 \(x\) 分别处理,每个儿子的子树染一个颜色,然后这个可以用莫队解决。

问题是 \(\sum siz_x\) 可能太大了,考虑把前 \(T\) 大的子树用暴力计算(后面说)。

类似HLD,前 \(T\) 大的儿子全部都是重儿子,那每跳一次轻边,子树大小至少扩大 \(T+1\) 倍。

\(T = n^{1/k}\)\(\sum siz\) 就变成 \(O(kn)\) 了,这样莫队复杂度就对了。

然后对于暴力,就是要做 \(O(nT)\) 次询问子树内 \([l, r]\) 的点数,用dfs序搞一下变成二维数点,可以用扫描线加 \(O(\sqrt n)\) - \(O(1)\) 数据结构算一下。

但是这题空间比较小,不能把 \(O(nT)\) 个询问全部存下来。

考虑把dfs序那维作为扫描,这样的话,每个子树的dfs序是不相交的,每个询问只需要记录一个值。

constexpr int N(2e5 + 5), T(20);
int n, m, r, siz[N], in[N], id[N], dfn;
std::vector<int> g[N];
void dfs(int x) {
  siz[x] = 1;
  in[x] = ++dfn, id[in[x]] = x;
  for (int y : g[x]) {
    g[y].erase(std::find(g[y].begin(), g[y].end(), x));
    dfs(y);
    siz[x] += siz[y];
  }
  std::sort(g[x].begin(), g[x].end(), [&](int i, int j) {
    return siz[i] > siz[j];
  });
}

struct Fenw {
  int c[N];
  void add(int p, int x) {
    for (; p <= n; p += p & -p) c[p] += x;
  }
  int ask(int p) {
    int r = 0;
    for (; p; p -= p & -p) r += c[p];
    return r;
  }
  int ask(int l, int r) {
    return ask(r) - ask(l - 1);
  }
} fen;


struct Qry {
  int l, r, x, id;
} q[N], tmp[N];
int cx[N], v0[N], v1[N];
LL ans[N];
void dfs2(int x) {
  for (int j = cx[x]; j < cx[x + 1]; j++) {
    v0[j] = fen.ask(q[j].l, q[j].r);
  }
  fen.add(x, 1);
  for (int i = 0; i < g[x].size(); i++) {
    int y = g[x][i];

    if (i < T) {
      for (int j = cx[x]; j < cx[x + 1]; j++) {
        v1[j] = fen.ask(q[j].l, q[j].r);
      }
      dfs2(y);
      for (int j = cx[x]; j < cx[x + 1]; j++) {
        int v = fen.ask(q[j].l, q[j].r) - v1[j];
        ans[q[j].id] -= 1LL * v * v;
      }
    } else {
      dfs2(y);
    }
  }
  for (int j = cx[x]; j < cx[x + 1]; j++) {
    int v = fen.ask(q[j].l, q[j].r) - v0[j];
    ans[q[j].id] += 1LL * v * v;
  }
}

int bel[N];
void work(int x) {
  int deg = g[x].size();
  if (deg <= T || cx[x] == cx[x + 1]) return;

  // std::cerr << "wk " << x << "\n";
  int sz = 0;
  for (int i = T; i < deg; i++) {
    int y = g[x][i];
    for (int j = in[y]; j < in[y] + siz[y]; j++) {
      v0[j] = i - T;
      v1[++sz] = id[j];
    }
  }
  std::sort(v1 + 1, v1 + 1 + sz);
  v1[0] = 0, v1[sz + 1] = n + 1;
  
  int B = sz / sqrt(cx[x + 1] - cx[x]);
  smax(B, 1);
  for (int i = 1, p = 0; i <= sz + 1; i++) {
    int x = (i - 1) / B;
    while (p < v1[i]) bel[++p] = x;
  }

  std::sort(q + cx[x], q + cx[x + 1], [&](const auto &i, const auto &j) {
    return bel[i.l] == bel[j.l] ? bel[i.l] & 1 ? i.r < j.r : i.r > j.r : i.l < j.l;
  });

  LL now = 0;

  std::vector<int> s(deg - T), t(sz);
  for (int i = 0; i < sz; i++) t[i] = v0[in[v1[i + 1]]];
  
  auto add = [&](int x) {
    now += 2 * ++s[t[x - 1]] - 1;
  };

  auto del = [&](int x) {
    now -= 2 * --s[t[x - 1]] + 1;
  };
  
  int l = 1, r = 0;

  for (int i = cx[x]; i < cx[x + 1]; i++) {
    while (v1[l - 1] >= q[i].l) add(--l);
    while (v1[r + 1] <= q[i].r) add(++r);
    while (v1[l] < q[i].l) del(l++);
    while (v1[r] > q[i].r) del(r--);
    ans[q[i].id] -= now;
  }
}

void solve() {
  cin >> n >> m >> r;
  for (int i = 1, x, y; i < n; i++) {
    cin >> x >> y;
    g[x].push_back(y);
    g[y].push_back(x);
  }
  dfs(r);

  for (int i = 0; i < m; i++) {
    int l, r, x;
    cin >> l >> r >> x;
    cx[x]++;
    tmp[i] = {l, r, x, i};
    if (l <= x && x <= r) ans[i]--;
  }
  for (int i = 1; i <= n; i++) cx[i + 1] += cx[i];
  for (int i = 0; i < m; i++) q[--cx[tmp[i].x]] = tmp[i];

  dfs2(r);

  for (int i = 1; i <= n; i++) {
    work(i);
  }

  for (int i = 0; i < m; i++) {
    cout << ans[i] / 2 << "\n";
  }
}

P7983 [JRKSJ R3] practiceZ

琴琴给了你两个长为 \(n\) 的序列 \(a,b\),请你支持三种操作共 \(m\) 次:

  1. 1 l r x,将 \(a\) 序列的区间 \([l,r]\) 中的所有数修改为 \(x\)
  2. 2 l r y,将 \(b\) 序列的区间 \([l,r]\) 中的所有数修改为 \(y\)
  3. 3 l r,求 \(\sum_{i=l}^r \sum_{j=1}^{b_i} a_j\)。答案对 \(2^{32}\) 取模。

对于 \(100\%\) 的数据,\(1\le n\le 5\times 10^5\)\(1\le m\le 3\times 10^5\)\(1\le a_i,x\le 10^9\)\(1\le b_i,y\le n\)

没什么营养的题。

首先观察询问,发现询问是对 b 序列的区间询问,因此可以对 b 分块。

对 a 的操作都是区间颜色段均摊,因而转换成 \(O(m)\) 次区间加减。

分块后,对于两边的散块,暴力。于是我们需要对于 a 维护一种支持 \(O(\sqrt N)\) 区间加,\(O(1)\) 查询前缀和的数据结构。

此处有一个关键的卡常点:往距离短的那个块边界暴力,这样可以减少一半的常数。

处理完散块后,所有的询问都是对整块的询问了,我们离线之后逐块处理。

在一个块中,维护 b 的颜色段,不是整块的颜色段每个操作只会产生 \(O(1)\) 个,所以所有块的散段个数是 \(O(m)\) 个,需要支持对这些散段进行 a 的前缀和查询,以及每块处理时进行的 \(O(m)\) 次对 a 的修改,维护一个 \(O(1)\) - \(O(\sqrt N)\) 的数据结构即可。

而对于是整块的颜色段,需要在逐块处理前查询好其对应的 a 的前缀和,并在覆盖整段时打上标记。

修改 a 时,需要查询这个东西对当前块的影响,如果当前整块是相同颜色,那么可以直接计算,否则需要计算的东西和之前类似,需要维护一个 \(O(\sqrt N)\) 区间修改,\(O(1)\) 查询的数据结构。

上面说的这一堆东西都是根号平衡的。

逐块处理的常数很大,所以块大小要开大点。

我最先的提交有些细节没实现好,复杂度是错的,居然也能过,可见上述的关键卡常点效果显著。

namespace FS {
using namespace std;
/* 64分木。
insert, erase
[]での存在判定
next, prev
*/
struct FastSet {
  using uint = unsigned;
  using ull = unsigned long long;

  int bsr(ull x) { return 63 - __builtin_clzll(x); }
  int bsf(ull x) { return __builtin_ctzll(x); }

  static constexpr uint B = 64;
  int n, lg;
  vector<vector<ull>> seg;
  FastSet(int _n) : n(_n) {
    do {
      seg.emplace_back((_n + B - 1) / B, -1ULL);
      _n = (_n + B - 1) / B;
    } while (_n > 1);
    lg = int(seg.size());
  }
  bool operator[](int i) const { return (seg[0][i / B] >> (i % B) & 1) != 0; }
  void insert(int i) {
    for (int h = 0; h < lg; h++) {
      seg[h][i / B] |= 1ULL << (i % B);
      i /= B;
    }
  }
  void erase(int i) {
    for (int h = 0; h < lg; h++) {
      seg[h][i / B] &= ~(1ULL << (i % B));
      if (seg[h][i / B]) break;
      i /= B;
    }
  }

  // x以上最小の要素を返す。存在しなければ n。
  int next(int i) {
    for (int h = 0; h < lg; h++) {
      if (i / B == seg[h].size()) break;
      ull d = seg[h][i / B] >> (i % B);
      if (!d) {
        i = i / B + 1;
        continue;
      }
      // find
      i += bsf(d);
      for (int g = h - 1; g >= 0; g--) {
        i *= B;
        i += bsf(seg[g][i / B]);
      }
      return i;
    }
    return n;
  }

  // x以下最大の要素を返す。存在しなければ -1。
  int prev(int i) {
    if (i < 0) return -1;
    if (i >= n) i = n - 1;
    for (int h = 0; h < lg; h++) {
      if (i == -1) break;
      ull d = seg[h][i / B] << (63 - i % 64);
      if (!d) {
        i = i / B - 1;
        continue;
      }
      // find
      i += bsr(d) - (B - 1);
      for (int g = h - 1; g >= 0; g--) {
        i *= B;
        i += bsr(seg[g][i / B]);
      }
      return i;
    }
    return -1;
  }

  // [l, r) 内の要素を全部集める
  vector<int> collect(int l, int r) {
    vector<int> res;
    int x = l - 1;
    while (1) {
      x = next(x + 1);
      if (x >= r) break;
      res.emplace_back(x);
    }
    return res;
  }
};
}

constexpr int N(5e5 + 5), M(3e5 + 5), N1((N >> 5) + 1), N2((N >> 10) + 1), N3((N >> 15) + 1), B(3500);
int n, m, a[N], b[N], ta[N], tb[N];

template <class T>
struct SumDS1 {
  T s0[N + 33], s1[N1 + 33], s2[N2 + 33], s3[N3 + 5];
  void clear() {
    memset(s0, 0, sizeof s0);
    memset(s1, 0, sizeof s1);
    memset(s2, 0, sizeof s2);
    memset(s3, 0, sizeof s3);
  }
  void add(int p, T x) {
    s0[p++] += x;
#define F(s0)                                                            \
    switch (p & 3) {                                                     \
      case 0: break;                                                     \
      case 1: s0[p] += x, s0[p + 1] += x, s0[p + 2] += x; p += 3; break; \
      case 2: s0[p] += x, s0[p + 1] += x; p += 2; break;                 \
      case 3: s0[p++] += x; break;                                       \
    }
#define G(s0) while (p & 31) s0[p] += x, s0[p + 1] += x, s0[p + 2] += x, s0[p + 3] += x, p += 4;

    F(s0)G(s0)p >>= 5;
    F(s1)G(s1)p >>= 5;
    F(s2)G(s2)p >>= 5;
    F(s3)while (p < N3) s3[p] += x, s3[p + 1] += x, s3[p + 2] += x, s3[p + 3] += x, p += 4;
#undef F
#undef G
  }
  T ask(int p) {
    return s0[p] + s1[p >> 5] + s2[p >> 10] + s3[p >> 15];
  }
};

template <class T>
struct SumDS2 {
  T s0[N], s1[N1], s2[N2], s3[N3];
  void clear() {
    memset(s0, 0, sizeof s0);
    memset(s1, 0, sizeof s1);
    memset(s2, 0, sizeof s2);
    memset(s3, 0, sizeof s3);
  }
  void add(int p, T x) {
    s0[p] += x, s1[p >> 5] += x, s2[p >> 10] += x, s3[p >> 15] += x;
  }
  T ask(int p) {
    T r{};
    int k = p & ~31;
    while (p >= k) r += s0[p--];
    if (p < 0) return r;
    p >>= 5, k = p & ~31;
    while (p >= k) r += s1[p--];
    if (p < 0) return r;
    p >>= 5, k = p & ~31;
    while (p >= k) r += s2[p--];
    if (p < 0) return r;
    p >>= 5;
    while (p >= 0) r += s3[p--];
    return r;
  }
};

using U2 = __attribute((vector_size(4 * 2))) U;
struct Opt {
  int opt, l, r, x;
  U y;
} op[N * 4];
int on;

U ans[M];
SumDS1<U2> ds1; 
SumDS2<U2> ds2;

U askA(int i) { // O(T)
  auto s = ds2.ask(i);
  return s[0] * (1 + i) - s[1];
}
void addA(int i, U x) { // O(1)
  ds2.add(i, U2{x, x * i});
}

U askA1(int i) { // O(1)
  auto s = ds1.ask(i);
  return s[0] * (1 + i) - s[1];
}
void addA1(int i, U x) { // O(T)
  ds1.add(i, U2{x, x * i});
}

void addB(int i, U x) { // O(T)
  ds1.add(n - i + 1, U2{x, x * i});  
}

void work(int l, int r) {
  int tagB = 0;
    
  U len = r - l + 1, now = 0;

  auto askB = [&](int i) -> U { // O(1)
    if (tagB) return i <= tagB ? (tagB - i + 1) * len : 0;
    auto s = ds1.ask(n - i + 1);
    return s[0] * (1 - i) + s[1];
  };

  FS::FastSet s(r - l + 1);

  auto split = [&](int x) {
    if (!s[x]) b[x + l] = b[s.prev(x) + l], s.insert(x);
  };
  
  ds1.clear();
  for (int i = l; i <= r; i++) {
    addB(b[i], 1);
  }

  for (int i = 1; i <= n; i++) {
    now += (a[i] - a[i - 1]) * askB(i);
  }

  ds2.clear();
  for (int i = 1; i <= n; i++) {
    addA(i, a[i] - a[i - 1]);
  }
  
  for (int i = 0; i < on; i++) {
    if (op[i].opt == 1) {
      now += op[i].x * askB(op[i].l);
      addA(op[i].l, op[i].x);
    } else {
      if (op[i].l > r || op[i].r < l) continue;
      int x = std::max(l, op[i].l), y = std::min(r, op[i].r);
      if (op[i].opt == 2) {
        if (tagB && x == l && y == r) {
          tagB = 0;
          now = 0;
        } else {
          if (tagB) addB(tagB, len), tagB = 0;
          split(x - l);
          if (y != r) split(y + 1 - l);
          for (int i = x - l, j, u, w; i < y + 1 - l; i = j) {
            u = i + l, j = s.next(i + 1), w = i - j;
            addB(b[u], w);
            now += w * askA(b[u]);
            if (u > x) s.erase(i);
          }
        }
        b[x] = op[i].x;
        if (x == l && y == r) {
          tagB = b[x];
        } else {
          addB(b[x], y - x + 1);
        }
        now += (y - x + 1) * op[i].y;
      } else {
        ans[op[i].x] += now;
      }
    }
  }
}

void solve() {
  cin >> n >> m;

  for (int i = 1; i <= n; i++) {
    cin >> a[i];
    addA1(i, a[i] - a[i - 1]);
  }
  for (int i = 1; i <= n; i++) {
    cin >> b[i];
  }

  memcpy(ta, a, sizeof a);
  memcpy(tb, b, sizeof b);

  FS::FastSet sa(n + 10), sb(n + 10);

  auto splitA = [&](int x) {
    if (!sa[x]) a[x] = a[sa.prev(x)], sa.insert(x);
  };
  auto splitB = [&](int x) {
    if (!sb[x]) b[x] = b[sb.prev(x)], sb.insert(x);
  };
  
  int qc = 0;
  while (m--) {
    int opt, l, r, x;
    cin >> opt >> l >> r;
    if (opt == 1) {
      cin >> x;
      splitA(l), splitA(++r);
      
      int d = 0;
      for (int i = l; i < r; i = sa.next(i + 1)) {
        int u = i, w = x - a[u], o = d + w;
        if (o) {
          addA1(u, o);
          op[on++] = {1, u, 0, o};
        }
        d = -w;
        if (i > l) sa.erase(i);
      }
      if (d) {
        addA1(r, d);
        op[on++] = {1, r, 0, d};
      }
      a[l] = x;
    } else if (opt == 2) {
      cin >> x;
      splitB(l), splitB(r + 1);
      for (int i = sb.next(l + 1); i != r + 1; i = sb.next(i)) {
        b[i] = 0;
        sb.erase(i);
      }
      b[l] = x;
      op[on++] = {opt, l, r, x, askA1(x)};
    } else {
      x = qc++;

      auto cal = [&](int l, int r) {
        if (l > r) return 0u;
        int o = b[sb.prev(l)];
        U a = 0;
        for (int i = l; i <= r; i++) {
          a += askA1(b[i] ? o = b[i] : o);
        }
        return a;
      };
      
      if (r - l + 1 <= B * 2) {
        ans[x] += cal(l, r);
      } else {
        int kl = (l - 1) / B * B + 1, kr = kl + B - 1;
        if (l - kl << 1 < B) {
          ans[x] -= cal(kl, l - 1);
          l = kl;
        } else {
          ans[x] += cal(l, kr);
          l = kr + 1;
        }

        kl = (r - 1) / B * B + 1, kr = std::min(n, kl + B - 1);
        if (kr - r << 1 < B) {
          ans[x] -= cal(r + 1, kr);
          r = kr;
        } else {
          ans[x] += cal(kl, r);
          r = kl - 1;
        }
        op[on++] = {opt, l, r, x};
      }
    }
  }

  memcpy(a, ta, sizeof a);
  memcpy(b, tb, sizeof b);

  // std::cerr << (double)clock() / CLOCKS_PER_SEC << "\n";
  for (int i = 1, j; i <= n; i = j + 1) {
    j = std::min(i + B - 1, n);
    work(i, j);
    // std::cerr << (double)clock() / CLOCKS_PER_SEC << "\n";
  }

  for (int i = 0; i < qc; i++) {
    cout << ans[i] << '\n';
  }

  std::cerr << (double)clock() / CLOCKS_PER_SEC << "\n";
}

End


最后修改于 2022-10-25