珂朵莉树

珂朵莉树思想大致为存储连续段信息,用 set 维护,再利用 set 特性暴力处理操作。比较擅长需要进行区间赋值、推平操作类型操作的题目,进行 \(k\) 次赋值、合并操作后的期望复杂度为 \(O(\frac{n}{k})\)详细证明,但可以粗略地记下他的复杂度:对于普通的区间加、区间赋值、简单的区间查询类操作,可以做到 \(O(n\log\log n)\) 的优秀期望复杂度。因此在随机数据下表现很好。作者偏爱一些比较暴力的数据结构,而在一些更加细节、有意思的部分认真思考,因此很久前学过。如今来重温练习一番。

板子

portal

题意:给定一个长度为 \(n\) 的序列,要进行 \(m\) 次操作:区间加、区间赋值、区间 kth 和取模意义下 \(x\) 次方和。\(1\leq n,q\leq 10^5\)

Code
// stODDDDDDDDDDDDDDDDDDDDDDDDDDD hzt CCCCCCCCCCCCCCCCCCCCCCCCCOrz

#include <algorithm>
#include <iostream>
#include <numeric>
#include <set>
#include <vector>

using namespace std;
using LL = long long;
using PII = pair<int, int>;
using PLI = pair<LL, int>;
constexpr int kN = 1e5 + 1, kP = 1e9 + 7;

int n, m, seed, vmx;
int a[kN];

struct Itv {
  int l, r;
  mutable LL w;
  Itv(int _l = 0, int _r = 0, LL _w = 0) { l = _l, r = _r, w = _w; }
  bool operator<(const Itv &x) const { return l < x.l; }
};
set<Itv> t;
auto S(int p) {
  auto i = t.lower_bound(Itv(p));
  if (i != t.end() && i->l == p) {
    return i;
  }
  i--;
  if (i->r < p) {
    return t.end();
  }
  int l = i->l, r = i->r;
  LL w = i->w;
  t.erase(i);
  t.emplace(l, p - 1, w);
  return t.emplace(p, r, w).first;
}
void U(int l, int r, int w) {
  auto ir = S(r + 1), il = S(l);
  for (auto i = il; i != ir; i++) {
    i->w += w;
  }
}
void A(int l, int r, int w) {
  auto ir = S(r + 1), il = S(l);
  t.erase(il, ir);
  t.emplace(l, r, w);
}
LL K(int l, int r, int w) {
  auto ir = S(r + 1), il = S(l);
  vector<PLI> v;
  for (auto i = il; i != ir; i++) {
    v.emplace_back(i->w, i->r - i->l + 1);
  }
  sort(v.begin(), v.end());
  for (auto [c, l] : v) {
    if (l < w) {
      w -= l;
    } else {
      return c;
    }
  }
  return -1;
}
int P(LL a, int b, int p) {
  int ret = 1;
  a %= p;
  for (; b > 0; b /= 2) {
    if (b & 1) {
      ret = ret * a % p;
    }
    a = a * a % p;
  }
  return ret;
}
LL Q(int l, int r, int x, int y) {
  auto ir = S(r + 1), il = S(l);
  LL ret = 0;
  for (auto i = il; i != ir; i++) {
    ret = (ret + 1ll * P(i->w, x, y) * (i->r - i->l + 1) % y) % y;
  }
  return ret;
}

int main() {
  cin.tie(0)->sync_with_stdio(0);
  cin >> n >> m >> seed >> vmx;
  auto rnd = [&]() {
    int ret = seed;
    seed = (7ll * seed + 13) % kP;
    return ret;
  };
  for (int i = 1; i <= n; i++) {
    a[i] = rnd() % vmx + 1;
    t.emplace(i, i, a[i]);
  }
  for (int i = 1, o, l, r, x, y; i <= m; i++) {
    o = rnd() % 4 + 1;
    l = rnd() % n + 1, r = rnd() % n + 1;
    if (l > r) {
      swap(l, r);
    }
    if (o == 3) {
      x = rnd() % (r - l + 1) + 1;
    } else {
      x = rnd() % vmx + 1;
    }
    if (o == 4) {
      y = rnd() % vmx + 1;
    }
    if (o == 1) {
      U(l, r, x);
    } else if (o == 2) {
      A(l, r, x);
    } else if (o == 3) {
      cout << K(l, r, x) << '\n';
    } else {
      cout << Q(l, r, x, y) << '\n';
    }
  }
  return 0;
}

T1

portal

题意:给定一个长度为 \(n\) 小写字母串,进行 \(q\) 次操作:区间升/降序排序。问最终字符串。\(1\leq n\leq 10^5, 1\leq q\leq 5\times 10^4\)

Solution

修改时只需要把这段区间内的所有颜色统计个数,然后按照字母序一一放回去即可。

由于 ODT 中总共最多产生 \(n + 26q\) 个区间,总时间复杂度大致为 \(O((n + 26q)\log\log n)\),足以通过此题。

\(\\\)

Code
// stODDDDDDDDDDDDDDDDDDDDDDDDDDD hzt CCCCCCCCCCCCCCCCCCCCCCCCCOrz

#include <algorithm>
#include <iostream>
#include <numeric>
#include <set>
#include <vector>

using namespace std;
using LL = long long;
using PII = pair<int, int>;
constexpr int kN = 1e5 + 1, kA = 26;

int n, q;
string s;

struct Itv {
  int l, r;
  char c;
  Itv(int _l = 0, int _r = 0, char _c = '#') { l = _l, r = _r, c = _c; }
  bool operator<(const Itv &x) const { return l < x.l; }
};
set<Itv> t;
auto S(int p) {
  auto i = t.lower_bound(Itv(p));
  if (i != t.end() && i->l == p) {
    return i;
  }
  i--;
  if (i->r < p) {
    return t.end();
  }
  int l = i->l, r = i->r;
  char c = i->c;
  t.erase(i);
  t.emplace(l, p - 1, c);
  return t.emplace(p, r, c).first;
}
int b[kA];
void U(int l, int r, bool o) {
  auto ir = S(r + 1), il = S(l);
  for (auto i = il; i != ir; i++) {
    b[i->c - 'a'] += i->r - i->l + 1;
  }
  t.erase(il, ir);
  if (o) {
    for (int i = 0, j = l; i < kA; i++) {
      if (b[i]) {
        t.emplace(j, j + b[i] - 1, i + 'a');
        j += b[i], b[i] = 0;
      }
    }
  } else {
    for (int i = kA - 1, j = l; i >= 0; i--) {
      if (b[i]) {
        t.emplace(j, j + b[i] - 1, i + 'a');
        j += b[i], b[i] = 0;
      }
    }
  }
}

int main() {
  cin.tie(0)->sync_with_stdio(0);
  cin >> n >> q >> s;
  s = '#' + s;
  for (int i = 1; i <= n; i++) {
    t.emplace(i, i, s[i]);
  }
  for (int i = 1, l, r, o; i <= q; i++) {
    cin >> l >> r >> o;
    U(l, r, o);
  }
  for (auto v : t) {
    for (int i = v.l; i <= v.r; i++) {
      cout << v.c;
    }
  }
  return 0;
}

T2

portal

题意:给定一颗 \(n\) 个节点、以 \(1\) 为根的树,每个点有 \(0/1\) 作为权值,初始为 \(0\),进行 \(q\) 次操作:将点 \(x\) 子树内点赋值为 \(1\)、点 \(x\) 到根节点 \(1\) 的路径上点赋值为 \(0\)、单点查询权值。\(1\leq n,q\leq 5\times 10^5\)

Solution

考虑树剖。

于是操作一变成了区间赋值(子树内 dfs 序连续),操作二直接暴力跳重链,操作三直接找到点所在的区间返回权值就行了。

时间复杂度 \(O(n + q\log n\log\log n)\)

\(\\\)

Code
// STOOOOOOOOOOOOOOOOOOOOOOOOO hzt CCCCCCCCCCCCCCCCCCCCCCCORZ
#include <algorithm>
#include <iostream>
#include <numeric>
#include <set>
#include <vector>

using namespace std;
using LL = long long;
using PII = pair<int, int>;
constexpr int kN = 5e5 + 1;

int n, q;
vector<int> e[kN];
int dep[kN], sz[kN], f[kN];
int dfn[kN], dfc, tp[kN];

void D1(int x, int fa) {
  dep[x] = dep[fa] + 1;
  f[x] = fa, sz[x] = 1;
  for (auto v : e[x]) {
    if (v == fa) {
      continue;
    }
    D1(v, x);
    sz[x] += sz[v];
  }
}
void D2(int x, int fa, int top) {
  tp[x] = top, dfn[x] = ++dfc;
  int mx = -1;
  for (auto v : e[x]) {
    if (v != fa && sz[v] > sz[mx]) {
      mx = v;
    }
  }
  if (mx != -1) {
    D2(mx, x, top);
  }
  for (auto v : e[x]) {
    if (v != fa && v != mx) {
      D2(v, x, v);
    }
  }
}

struct Itv {
  int l, r;
  mutable bool w;
  Itv(int _l = 0, int _r = 0, bool _w = 0) { l = _l, r = _r, w = _w; }
  bool operator<(const Itv &x) const { return l < x.l; }
};
set<Itv> t;
auto S(int p) {
  auto i = t.lower_bound(p);
  if (i != t.end() && i->l == p) {
    return i;
  }
  i--;
  int l = i->l, r = i->r;
  bool w = i->w;
  t.erase(i);
  t.emplace(l, p - 1, w);
  return t.emplace(p, r, w).first;
}
void A(int l, int r, int w) {
  auto ir = S(r + 1), il = S(l);
  t.erase(il, ir);
  t.emplace(l, r, w);
}
void U(int u, int v, int w) {
  for (; tp[u] != tp[v]; u = f[tp[u]]) {
    if (dep[tp[u]] < dep[tp[v]]) {
      swap(u, v);
    }
    A(dfn[tp[u]], dfn[u], w);
  }
  if (dep[u] > dep[v]) {
    swap(u, v);
  }
  A(dfn[u], dfn[v], w);
}
int Q(int x) {
  auto i = t.lower_bound(x);
  if (i == t.end()) {
    return 0;
  } else if (i != t.end() && i->l == x) {
    return i->w;
  } else {
    return prev(i)->w;
  }
}

int main() {
  cin.tie(0)->sync_with_stdio(0);
  cin >> n;
  for (int i = 1, u, v; i < n; i++) {
    cin >> u >> v;
    e[u].emplace_back(v);
    e[v].emplace_back(u);
  }
  D1(1, 0), D2(1, 0, 1);
  t.emplace(1, n, 0);
  cin >> q;
  for (int _ = 1, o, x; _ <= q; _++) {
    cin >> o >> x;
    if (o == 1) {
      A(dfn[x], dfn[x] + sz[x] - 1, 1);
    } else if (o == 2) {
      U(x, 1, 0);
    } else {
      cout << Q(dfn[x]) << '\n';
    }
  }
  return 0;
}

T3

portal

题意:给定一个长度为 \(n\) 的序列 \(b\) 和一个整数 \(k\),将 \(b\) 序列复制 \(k\) 次成为长度为 \(nk\) 的初始序列 \(a\)。有 \(q\) 次操作:区间赋值、区间取最小值。\(1\leq n, q\leq 10^5, 1\leq k\leq 10^4\)

Solution

序列长度很长,但维护信息不多,重复量很大。考虑用 ODT 维护修改,而未修改部分使用 ST 表计算即可。时间复杂度 \(O(n\log n + q\log\logn)\)

\(\\\)

Code
// STOOOOOOOOOOOOOOOOOOOOOOOOO hzt CCCCCCCCCCCCCCCCCCCCCCCORZ
#include <algorithm>
#include <iostream>
#include <numeric>
#include <set>
#include <vector>

using namespace std;
using LL = long long;
using PII = pair<int, int>;
constexpr int kN = 1e5 + 2, kB = 17, kI = 1e9;

int n, k, q;
int f[kB][kN], lg[kN], s[kN], p[kN], bmn = kI, mn = kI;
int Min(int l, int r) {
  if (r - l + 1 >= n) {
    return bmn;
  }
  l = (l - 1) % n + 1, r = (r - 1) % n + 1;
  if (l > r) {
    return min(p[r], s[l]);
  }
  int g = lg[r - l + 1];
  return min(f[g][l], f[g][r - (1 << g) + 1]);
}

struct Itv {
  int l, r, w;
  bool t;
  Itv(int _l = 0, int _r = 0, int _w = 0, bool _t = 0) { l = _l, r = _r, w = _w, t = _t; }
  bool operator<(const Itv &x) const {
    return l < x.l;
  }
};
set<Itv> t;
auto S(int p) {
  auto i = t.lower_bound(p);
  if (i != t.end() && i->l == p) {
    return i;
  }
  i--;
  if (i->r < p) {
    return t.end();
  }
  int l = i->l, r = i->r, w = i->w;
  bool f = i->t;
  t.erase(i);
  t.emplace(l, p - 1, w, f);
  return t.emplace(p, r, w, f).first;
}
void A(int l, int r, int w) {
  auto ir = S(r + 1), il = S(l);
  t.erase(il, ir);
  t.emplace(l, r, w, 1);
}
int Q(int l, int r) {
  auto ir = S(r + 1), il = S(l);
  int ret = kI;
  for (auto i = il; i != ir; i++) {
    if (i->t) {
      ret = min(ret, i->w);
    } else {
      ret = min(ret, Min(i->l, i->r));
    }
    if (ret == mn) {
      break;
    }
  }
  return ret;
}

int main() {
  cin.tie(0)->sync_with_stdio(0);
  cin >> n >> k;
  for (int i = 1; i <= n; i++) {
    cin >> f[0][i];
    bmn = min(bmn, f[0][i]);
  }
  mn = bmn, lg[0] = -1;
  for (int i = 1; i <= n; i++) {
    lg[i] = lg[i / 2] + 1;
  }
  p[0] = s[n + 1] = kI;
  for (int i = 1; i <= n; i++) {
    p[i] = min(p[i - 1], f[0][i]);
  }
  for (int i = n; i >= 1; i--) {
    s[i] = min(s[i + 1], f[0][i]);
  }
  for (int i = 1; i < kB; i++) {
    for (int j = 1; j + (1 << i) <= n + 1; j++) {
      f[i][j] = min(f[i - 1][j], f[i - 1][j + (1 << i - 1)]);
    }
  }

  t.emplace(1, k * n, bmn, 0);
  cin >> q;
  for (int _ = 1, o, l, r, w; _ <= q; _++) {
    cin >> o >> l >> r;
    if (o == 1) {
      cin >> w;
      mn = min(mn, w);
      A(l, r, w);
    } else {
      cout << Q(l, r) << '\n';
    }
  }
  return 0;
}

T4

portal

题意:有一个 \(n\) 个元素的序列,每个元素有其相应的颜色 \(c_i\) 和值 \(w_i\),初始分别为 \(i\)\(0\)。将进行 \(m\) 次操作:区间修改(对于所有 \(i\in[l,r], w_i\) 加上 \(|c_i-x|\)\(c_i\) 变为 \(x\))、查询区间权值和。\(1\leq n,m\leq 10^5\)

Solution

还是可以 ODT。考虑用 ODT 维护颜色,线段树维护权值:修改在 ODT 上区间赋值时把区间的颜色和 \(x\) 的差放线段树上区间加,查询直接线段树上查询区间和就行了。时间复杂度 \(O(q\log^2n)\)

\(\\\)

Code
// STOOOOOOOOOOOOOOOOOOOOOOOOO hzt CCCCCCCCCCCCCCCCCCCCCCCORZ
#include <algorithm>
#include <iostream>
#include <numeric>
#include <set>
#include <vector>

using namespace std;
using LL = long long;
using PII = pair<int, int>;
constexpr int kN = 1e5 + 1;

int n, m;
struct Segment {
  struct Node {
    LL w, t;
  } t[4 * kN];
  void PU(int x) { t[x].w = t[2 * x].w + t[2 * x + 1].w; }
  void PD(int x, int l, int r) {
    t[2 * x].t += t[x].t, t[2 * x + 1].t += t[x].t;
    int m = (l + r) / 2;
    t[2 * x].w += t[x].t * (m - l + 1);
    t[2 * x + 1].w += t[x].t * (r - m);
    t[x].t = 0;
  }
  void U(int L, int R, int w, int x = 1, int l = 1, int r = n) {
    if (r < L || R < l) {
      return;
    } else if (L <= l && r <= R) {
      t[x].w += w * (r - l + 1ll), t[x].t += w;
      return;
    }
    int m = (l + r) / 2;
    PD(x, l, r);
    U(L, R, w, 2 * x, l, m);
    U(L, R, w, 2 * x + 1, m + 1, r);
    PU(x);
  }
  LL Q(int L, int R, int x = 1, int l = 1, int r = n) {
    if (r < L || R < l) {
      return 0;
    } else if (L <= l && r <= R) {
      return t[x].w;
    }
    int m = (l + r) / 2;
    PD(x, l, r);
    return Q(L, R, 2 * x, l, m) + Q(L, R, 2 * x + 1, m + 1, r);
  }
} st;

struct Itv {
  int l, r, c;
  Itv(int _l = 0, int _r = 0, int _c = 0) { l = _l, r = _r, c = _c; }
  bool operator<(const Itv &x) const { return l < x.l; }
};
set<Itv> t;
auto S(int p) {
  auto i = t.lower_bound(p);
  if (i != t.end() && i->l == p) {
    return i;
  }
  i--;
  if (i->r < p) {
    return t.end();
  }
  int l = i->l, r = i->r, c = i->c;
  t.erase(i);
  t.emplace(l, p - 1, c);
  return t.emplace(p, r, c).first;
} 
void A(int l, int r, int w) {
  auto ir = S(r + 1), il = S(l);
  for (auto i = il; i != ir; i++) {
    st.U(i->l, i->r, abs(i->c - w));
  }
  t.erase(il, ir);
  t.emplace(l, r, w);
}

int main() {
  cin.tie(0)->sync_with_stdio(0);
  cin >> n >> m;
  for (int i = 1; i <= n; i++) {
    t.emplace(i, i, i);
  }
  for (int i = 1, o, l, r, x; i <= m; i++) {
    cin >> o >> l >> r;
    if (o == 1) {
      cin >> x;
      A(l, r, x);
    } else {
      cout << st.Q(l, r) << '\n';
    }
  }
  return 0;
}

最后

可能是因为 ODT 太暴力了,现在出题人都不太看好这种做法。作者写了 CF1136ELG P5500 后都被卡飞,其实都挺有意思的。或许 ODT 只在诞生之时存活吧。还是线段树更实用一些。

posted @ 2023-11-02 20:12  Lightwhite  阅读(28)  评论(0)    收藏  举报