Solution Set #3

带 * 的是写得不太详细的题解,慎看!

P11648. 【MX-X8-T7】「TAOI-3」2236 A.D.(根号分治,根号重构,启发式合并,高维前缀和)

\(w_i\) 表示状态为 \(i\) 时的贡献。

首先用类似启发式合并的思想,对每个点维护一个可重集 \(S_i\) 表示 \(i\) 子树内的节点到 \(i\) 的所有路径状态构成的集合,那么我们需要支持合并两个集合 \(S_i\)\(S_j\) 同时计算 \(\sum_{x\in S_i}\sum_{y\in S_j}{w_{x|y}}\),以及让 \(S_i\) 内的全体元素或上 \(2^{c_i}\)

先考虑怎么合并两个集合,不妨设 \(|S_i|\leq|S_j|\)。这个东西一看就知道无法做到比根号更优,所以考虑根号分治,假设集合大小的阈值是 \(B\)。那么可以得到几个暴力:

  1. 如果 \(|S_i|\leq B,|S_j|\leq B\),暴力枚举 \(S_i\)\(S_j\) 分别选择的元素即可。
  2. 如果 \(|S_i|>B,|S_j|>B\),直接 FWT。
  3. 如果\(|S_i|\leq B,|S_j|>B\),暴力枚举 \(S_i\) 的元素 \(x\),则我们需要计算出 \(f_x=\sum_{y\in S_j}w_{x|y}\)。由于任意时刻大小大于 \(B\) 的集合数量只有至多 \(O(\frac{n}{B})\),所以通过适当调整阈值,就能够对每个大集合 \(|S_j|\),维护出数组 \(f_{j,x}=\sum_{y\in S_j}{w_{x|y}}\),合并的时候只需要调用 \(f_{j,x}\) 就可以知道每种数与 \(S_j\) 内的元素合并的贡献总和。

具体地,对于每个集合 \(S_i\),维护 \((f_i,T_i)\),其中 \(T_i\) 表示落单的元素,数量小于 \(B\) 个(具体含义后面会说),\(f_i\) 就是其余没有落单的元素构成的 \(f\) 数组。假设要合并 \(S_i\)\(S_j\)\(|S_i|\leq |S_j|\),那么暴力枚举 \(S_i\) 的所有元素 \(x\),通过 \(f_{j,x}\) 计算出所有 \(S_j\) 没落单元素与 \(S_i\) 的贡献之和。然后只需要考虑 \(S_j\) 落单元素 \(T_j\)\(S_i\) 的贡献之和,同样是暴力枚举 \(S_j\) 的元素 \(y\),与 \(S_i\) 没落单部分的贡献通过 \(f_{i,y}\) 计算,与落单部分的贡献暴力枚举计算。合并集合时,将所有 \(S_i\) 的元素放到 \(T_j\) 中,如果 \(T_j\) 的大小大于等于 \(B\) 则重构,将 \(T_j\) 部分的贡献放到 \(f_j\) 中。让 \(S_i\) 内的全体元素或上 \(2^{c_i}\) 这个操作可以通过懒标记来维护。

重构时需要计算 \(f_x=\sum_y{cnt_y\cdot w_{x|y}}\),不好直接计算。考虑预处理出 \(w\) 的高位后缀差分数组 \(w'\),问题变为 \(f_x=\sum_y{cnt_y\cdot\sum_{x\subseteq s,y\subseteq s}w'_s}=\sum_{x\subseteq s}w'_s\cdot\sum_{y\subseteq s}cnt_y\),容易通过一次高维前缀和以及一次高维后缀和求解,单次重构的复杂度是 \(O(2^kk)\)

下面计算一下时间复杂度:暴力枚举落单部分的不会超过 \(O(\sum_{i}\sum_j\min(sz_i,B)\cdot\min(sz_j,B))=O(nB)\),重构至多进行 \(O(\frac{n}{B})\) 次,所以总时间复杂度是 \(O(nB+\frac{n2^kk}{B})\),取 \(B=\sqrt{2^kk}\) 时即可。最终的时间复杂度为 \(O(n\sqrt{2^kk})\)

对于这类看起来很难根号分治的题,可以考虑用根号重构的思想,维护出落单集合以及非落单集合,合并时非落单集合与非落单集合合并,落单集合与落单集合合并,如果落单集合大小达到阈值就触发重构。

代码
#include <bits/stdc++.h>

// #define int int64_t

const int kMaxN = 5e5 + 5, kMaxK = 16, kMaxS = (1 << 15) + 5, kMod = 998244353;

int n, k, B;
int w[kMaxK], _val[kMaxS], c[kMaxN], val[kMaxS], id[kMaxN], res[kMaxN];
int dfn_cnt, sz[kMaxN], dfn[kMaxN], idx[kMaxN], p[kMaxN], pos[kMaxN];
std::vector<int> G[kMaxN];

struct Node {
  std::vector<int> f, small;
  int tag, sum, sz;
} t[kMaxN];

int qpow(int bs, int64_t idx = kMod - 2) {
  int ret = 1;
  for (; idx; idx >>= 1, bs = (int64_t)bs * bs % kMod)
    if (idx & 1)
      ret = (int64_t)ret * bs % kMod;
  return ret;
}

inline int add(int x, int y) { return (x + y >= kMod ? x + y - kMod : x + y); }
inline int sub(int x, int y) { return (x >= y ? x - y : x - y + kMod); }
inline void inc(int &x, int y) { (x += y) >= kMod ? x -= kMod : x; }
inline void dec(int &x, int y) { (x -= y) < 0 ? x += kMod : x; }

void rebuild(int x) {
  static int f[kMaxS];
  std::fill_n(f, 1 << k, 0);
  if (!t[x].f.size()) t[x].f.resize(1 << k);
  for (int s = (1 << k) - 1; ~s; --s) t[x].f[s] = t[x].f[s | t[x].tag];
  t[x].tag = 0;
  for (auto i : t[x].small) ++f[i];
  for (int i = 0; i < k; ++i)
    for (int s = 0; s < (1 << k); ++s)
      if (s >> i & 1)
        inc(f[s], f[s ^ (1 << i)]);
  for (int s = 0; s < (1 << k); ++s) f[s] = 1ll * f[s] * val[s] % kMod;
  for (int i = 0; i < k; ++i)
    for (int s = 0; s < (1 << k); ++s)
      if (~s >> i & 1)
        inc(f[s], f[s ^ (1 << i)]);
  // for (int i = 0; i < (1 << k); ++i)
  //   for (auto j : t[x].small)
  //     inc(f[i], _val[i | j]);
  for (int s = 0; s < (1 << k); ++s) inc(t[x].f[s], f[s]);
  std::vector<int>().swap(t[x].small);
}

int merge(int x, int y) {
  int u = pos[x], v = pos[y];
  std::vector<int> all;
  if (t[x].sz < t[y].sz) {
    std::function<void(int, int, int)> dfs = [&] (int u, int fa, int s) {
      all.emplace_back(s);
      for (auto w : G[u]) {
        if (w == fa) continue;
        if (w == v) break;
        dfs(w, u, s | (1 << c[w]));
      }
    };
    dfs(u, p[u], 1 << c[u]);
    std::swap(x, y), std::swap(u, v);
  } else {
    std::function<void(int, int, int)> dfs = [&] (int u, int fa, int s) {
      all.emplace_back(s);
      for (auto v : G[u]) {
        if (v == fa) continue;
        dfs(v, u, s | (1 << c[v]));
      }
    };
    dfs(v, u, (1 << c[u]) | (1 << c[v]));
  }
  assert(all.size() == t[y].sz);
  inc(t[x].sum, t[y].sum), t[x].sz += t[y].sz;
  if (t[x].f.size()) {
    for (auto i : all) inc(t[x].sum, t[x].f[i | t[x].tag]);
  }
  for (auto i : t[x].small) {
    if (t[y].f.size()) inc(t[x].sum, t[y].f[i | t[y].tag]);
    for (auto j : t[y].small)
      inc(t[x].sum, _val[i | j]);
  }
  for (auto i : all) t[x].small.emplace_back(i);
  if (t[x].small.size() >= B) rebuild(x);
  std::vector<int>().swap(t[y].f);
  std::vector<int>().swap(t[y].small);
  assert(t[x].small.size() < B);
  return x;
}

void dfs(int u, int fa) {
  sz[u] = 1, idx[dfn[u] = ++dfn_cnt] = u;
  p[u] = fa, pos[id[u] = u] = u, t[u].small = {1 << c[u]}, t[u].sz = 1, t[u].sum = _val[1 << c[u]];
  for (auto v : G[u]) {
    if (v == fa) continue;
    dfs(v, u);
    t[id[v]].tag |= (1 << c[u]);
    for (auto &i : t[id[v]].small) i |= (1 << c[u]);
    pos[id[u] = merge(id[u], id[v])] = u;
  }
  res[u] = t[id[u]].sum;
}

void dickdreamer() {
  std::cin >> n >> k; B = std::max(2, std::min<int>(n, sqrtl((1 << k) * k)));
  for (int i = 0; i < k; ++i) std::cin >> w[i];
  for (int i = 1; i <= n; ++i) std::cin >> c[i], --c[i];
  for (int i = 1; i < n; ++i) {
    int u, v;
    std::cin >> u >> v;
    G[u].emplace_back(v), G[v].emplace_back(u);
  }
  for (int s = 0; s < (1 << k); ++s) {
    val[s] = 1;
    for (int i = 0; i < k; ++i)
      if (s >> i & 1)
        val[s] = 1ll * val[s] * w[i] % kMod;
    _val[s] = val[s];
  }
  for (int i = 0; i < k; ++i)
    for (int s = 0; s < (1 << k); ++s)
      if (~s >> i & 1)
        dec(val[s], val[s ^ (1 << i)]);
  dfs(1, 0);
  for (int i = 1; i <= n; ++i) std::cout << res[i] << " \n"[i == n];
}

int32_t main() {
#ifdef ORZXKR
  freopen("in.txt", "r", stdin);
  freopen("out.txt", "w", stdout);
#endif
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  int T = 1;
  // std::cin >> T;
  while (T--) dickdreamer();
  // std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
  return 0;
}

P8852. [JRKSJ R5] Concvssion(长链剖分,根号分治,ntt)

首先如果 \(a_i=i\bmod n+1\),此时图是一个环,最终的答案 \(res_k=\sum i\times cnt_{(i-k)\bmod n}\),将 \(cnt\) 倍长后做一遍差卷积即可。如果有多个环,对于一个长度为 \(l\) 的环,最终这个环对 \(i\) 的贡献只跟 \(i\bmod l\) 有关。注意到 \(\sum l=n\),所以不同的 \(l\) 只有 \(O(\sqrt n)\) 种,对长度相同的环一起做即可。

再考虑 \(a_i\leq\max(i-1,1)\) 的情况,此时图构成一棵内向树,每个点走到 \(1\) 之后会一直贡献 \(1\)。先计算每个点第一次走到 \(1\) 之前的贡献。容易想到长链剖分,但是一个点 \(x\) 走的过程可能走出长链,导致最后贡献的时间长度会长于 \(x\) 所在的长链长度。一个想法是枚举一个长链,求出这个长链子树内的点走到这个长链上的贡献,由于长链子树内的每个点第一次走到这个长链上的位置并不固定,所以还是做不了。

既然直接无法求解,我们考虑通过调整来计算贡献。即如果要将 \(x\to root\) 的贡献改成 \(y\to root\),就要加上 \(x\to LCA(x, y)\) 这个路径的贡献减去 \(y\to LCA(x,y)\) 的贡献。放到长链剖分上就是从下往上调整,钦定 \(x\) 的子树内经过调整后的初始点都在 \(x\) 所在长链上,设 \(x\) 的长儿子是 \(y\),一个短儿子是 \(z\)\(y\) 所在长链从上到下第 \(i\) 个点是 \(Y_i\)\(z\) 所在长链从上到下第 \(i\) 个点是 \(Z_i\)\(y\) 子树内的初始点显然不需要进行调整,现在要将 \(z\) 长链上的点调整到 \(x\) 的长链上,那么调整的贡献就是 \(res_k\leftarrow res_k+\sum_{i\geq k+1} cnt_i\times(Z_{i-k}-Y_{i-k})\),调整完之后再将 \(z\) 对应的 \(cnt\) 加到 \(y\) 上。由于里面枚举的 \(i\) 不超过 \(z\) 的长链长度,所以这部分可以直接差卷积。最后所有初始点都在 \(1\) 的长链上,容易再通过一次卷积计算出答案。

最后考虑是普通基环树的情况。对于一棵基环树,先把每个环上点 \(x\) 对应子树的初始点都调整到一条 \(x\) 所在的长链上,再将环断成链,即可通过同样的方式将这些初始点都调整到环上。调整完之后做一遍初始全是环的做法即可,时间复杂度是 \(O(n\sqrt n)\)

对于这类跟链长有关的长链剖分问题,如果贡献可能大于长链长度,可以考虑一步一步将短链上的贡献调整到长链上,将短链上的点映射到长链上深度相同的,这样每次调整的长度就只跟短链长度有关了。

代码
#include <bits/stdc++.h>

// #define int int64_t

const int kMaxN = 3e5 + 5, kMod = 998244353;

int n, cntr;
int a[kMaxN], b[kMaxN], cnt[kMaxN], res[kMaxN];
int pos[kMaxN], len[kMaxN], lson[kMaxN], sum[kMaxN];
bool vis[kMaxN], onr[kMaxN];
std::vector<int> G[kMaxN], ring[kMaxN];

constexpr int qpow(int bs, int64_t idx = kMod - 2) {
  int ret = 1;
  for (; idx; idx >>= 1, bs = (int64_t)bs * bs % kMod)
    if (idx & 1)
      ret = (int64_t)ret * bs % kMod;
  return ret;
}

inline int add(int x, int y) { return (x + y >= kMod ? x + y - kMod : x + y); }
inline int sub(int x, int y) { return (x >= y ? x - y : x - y + kMod); }
inline void inc(int &x, int y) { (x += y) >= kMod ? x -= kMod : x; }
inline void dec(int &x, int y) { (x -= y) < 0 ? x += kMod : x; }

namespace POLY {
constexpr int kMaxN = 4e6 + 5, kR = 3, kB = __builtin_ctz(kMod - 1), kG = qpow(kR, (kMod - 1) >> kB);

int polyg[kMaxN];
bool inited;

void prework(int n = (kMaxN - 5) / 2) {
  inited = 1;
  int c = 0;
  for (; (1 << c) <= n; ++c) {}
  c = std::min(c - 1, kB - 2);
  polyg[0] = 1, polyg[1 << c] = qpow(kG, 1 << (kB - 2 - c));
  for (int i = c; i; --i)
    polyg[1 << i - 1] = (int64_t)polyg[1 << i] * polyg[1 << i] % kMod;
  for (int i = 1; i < (1 << c); ++i) 
    polyg[i] = (int64_t)polyg[i & (i - 1)] * polyg[i & -i] % kMod;
}

int getlen(int n) {
  int len = 1;
  for (; len <= n; len <<= 1) {}
  return len;
}

struct Poly : std::vector<int> {
  using vector::vector;
  using vector::operator [];

  friend Poly operator -(Poly a) {
    static Poly c;
    c.resize(a.size());
    for (int i = 0; i < c.size(); ++i)
      c[i] = sub(0, c[i]);
    return c;
  }
  friend Poly operator +(Poly a, Poly b) {
    static Poly c;
    c.resize(std::max(a.size(), b.size()));
    for (int i = 0; i < c.size(); ++i)
      c[i] = add((i < a.size() ? a[i] : 0), (i < b.size() ? b[i] : 0));
    return c;
  }
  friend Poly operator -(Poly a, Poly b) {
    static Poly c;
    c.resize(std::max(a.size(), b.size()));
    for (int i = 0; i < c.size(); ++i)
      c[i] = sub((i < a.size() ? a[i] : 0), (i < b.size() ? b[i] : 0));
    return c;
  }
  friend void dif(Poly &a, int len) {
    if (a.size() < len) a.resize(len);
    for (int l = len; l != 1; l >>= 1) {
      int m = l / 2;
      for (int i = 0, k = 0; i < len; i += l, ++k) {
        for (int j = 0; j < m; ++j) {
          int tmp = (int64_t)a[i + j + m] * polyg[k] % kMod;
          a[i + j + m] = sub(a[i + j], tmp);
          inc(a[i + j], tmp);
        }
      }
    }
  }
  friend void dit(Poly &a, int len) {
    if (a.size() < len) a.resize(len);
    for (int l = 2; l <= len; l <<= 1) {
      int m = l / 2;
      for (int i = 0, k = 0; i < len; i += l, ++k) {
        for (int j = 0; j < m; ++j) {
          int tmp = a[i + j + m];
          a[i + j + m] = (int64_t)sub(a[i + j], tmp) * polyg[k] % kMod;
          inc(a[i + j], tmp);
        }
      }
    }
    int invl = qpow(len);
    for (int i = 0; i < len; ++i)
      a[i] = (int64_t)a[i] * invl % kMod;
    std::reverse(a.begin() + 1, a.begin() + len);
  }
  friend Poly operator *(Poly a, Poly b) {
    if (!inited) prework();
    int n = a.size() + b.size() - 1, len = getlen(n);
    a.resize(len), b.resize(len);
    dif(a, len), dif(b, len);
    for (int i = 0; i < len; ++i)
      a[i] = (int64_t)a[i] * b[i] % kMod;
    dit(a, len);
    a.resize(n);
    return a;
  }
  friend Poly operator *(Poly a, int b) {
    static Poly c;
    c = a;
    for (auto &x : c) x = (int64_t)x * b % kMod;
    return c;
  }
  friend Poly operator *(int a, Poly b) {
    static Poly c;
    c = b;
    for (auto &x : c) x = (int64_t)x * a % kMod;
    return c;
  }
  friend void operator *=(Poly &a, Poly b) {
    if (!inited) prework();
    int n = a.size() + b.size() - 1, len = getlen(n);
    a.resize(len), b.resize(len);
    dif(a, len), dif(b, len);
    for (int i = 0; i < len; ++i)
      a[i] = (int64_t)a[i] * b[i] % kMod;
    dit(a, len);
    a.resize(n);
  }
};
} // namespace POLY

using POLY::Poly;

Poly s[kMaxN];

Poly submul(Poly a, Poly b) {
  if (!b.size()) return {};
  // c[k] = sum a[i] * b[i - k]
  std::reverse(b.begin(), b.end());
  a *= b;
  return Poly(a.begin() + b.size() - 1, a.end());
}

void dfs1(int u, int fa) {
  // std::cerr << "??? " << u << ' ' << fa << '\n';
  vis[u] = 1;
  for (auto v : G[u]) {
    if (v == fa || onr[v]) continue;
    dfs1(v, u);
    if (len[v] + 1 > len[u]) len[u] = len[v] + 1, lson[u] = v;
  }
}

void dfs2(int u, int fa) {
  // std::cerr << u << ' ' << fa << '\n';
  if (lson[u]) dfs2(lson[u], u);
  for (auto v : G[u]) {
    if (v == fa || onr[v] || v == lson[u]) continue;
    dfs2(v, u);
    // std::cerr << u << ' ' << v << ' ' << len[v] << '\n';
    Poly f(len[v] + 1), g(len[v] + 1);
    for (int i = v, j = lson[u], d = 0; i; i = lson[i], j = lson[j], ++d) {
      // int to = idx[dfn[u] + d + 1];
      // f[d] = cnt[i], g[d] = sub(i, to), cnt[to] += cnt[i];
      f[d] = cnt[i], g[d] = sub(i, j), cnt[j] += cnt[i];
    }
    f = submul(f, g);
    for (int i = 0; i < f.size(); ++i) inc(res[i], f[i]);
  }
}

void dickdreamer() {
  std::cin >> n;
  for (int i = 1; i <= n; ++i) {
    std::cin >> a[i];
    G[a[i]].emplace_back(i), G[i].emplace_back(a[i]);
  }
  for (int i = 1; i <= n; ++i) std::cin >> b[i], ++cnt[b[i]];
  // auto poly = submul({1, 2, 3}, {1, 2, 3});
  // for (auto x : poly) std::cerr << x << ' ';
  // std::cerr << '\n';
  for (int c = 1; c <= n; ++c) {
    static int pre[kMaxN];
    if (vis[c]) continue;
    int rt = c;
    for (; !vis[rt]; rt = a[rt]) vis[rt] = 1;
    ++cntr;
    // std::cerr << i << ' ' << rt << '\n';
    for (int i = rt;; i = a[i]) {
      pos[i] = ring[cntr].size(), onr[i] = 1;
      ring[cntr].emplace_back(i), pre[a[i]] = i;
      if (a[i] == rt) break;
    }
    int sz = ring[cntr].size();
    for (auto u : ring[cntr]) {
      dfs1(u, 0), dfs2(u, 0);
      inc(sum[sz], u);
      int v = lson[u];
      Poly f(len[v] + 1), g(len[v] + 1);
      for (int i = v, j = pre[u], d = 0; i; i = lson[i], j = pre[j], ++d) {
        assert(!onr[i]);
        f[d] = cnt[i], g[d] = sub(i, j), cnt[j] += cnt[i];
      }
      f = submul(f, g);
      for (int i = 0; i < f.size(); ++i) inc(res[i], f[i]);
    }
    Poly f(2 * sz), g(sz);
    for (int i = 0; i < sz; ++i) {
      int u = ring[cntr][i];
      f[i] = f[i + sz] = u, g[i] = cnt[u];
    }
    f = submul(f, g);
    assert(f.size() >= sz + 1);
    // f.resize(sz);
    // for (int i = 0; i < f.size(); ++i) std::cerr << (i < sz) << ' ' << f[i] << '\n';
    // std::cerr << f.size() << ' ' << sz << '\n';
    f.resize(sz);
    s[sz] = s[sz] + f;
    // std::cerr << sz << ' ' << f[0] << ' ' << tmp << '\n';
  }
  for (int i = 1; i <= n; ++i) {
    if (s[i].size()) {
      for (int j = 1; j <= n; ++j) inc(res[j], s[i][j % i]);
    }
  }
  // for (int i = 1; i <= n; ++i) {
  //   if (onr[i]) {
  //     for (int j = i, k = 0; k <= n; j = a[j], ++k)
  //       inc(res[k], 1ll * j * cnt[i] % kMod);
  //   }
  // }
  // for (int i = 1; i <= n; ++i) std::cerr << res[i] << " \n"[i == n];
  for (int i = 1; i <= n; ++i) std::cout << res[i] << '\n';
}

int32_t main() {
#ifdef ORZXKR
  freopen("in.txt", "r", stdin);
  freopen("out.txt", "w", stdout);
#endif
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  int T = 1;
  // std::cin >> T;
  while (T--) dickdreamer();
  // std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
  return 0;
}

P12704. Retribution(图论,强连通分量,可达性判断,猜结论,adhoc)

先把图建出来,记录 \(S_{x,y}\) 表示能到达 \((x,y)\) 的点构成的集合,然后会发现 \(S(x,y)\) 一定是个矩形,这是因为如果不是矩形,则一定会出现类似 \((a,b)\notin S_{x,y},(a,b+1)\in S,(a+1,b)\in S\) 的形状,由于 \((a,b)\) 会与 \((a,b+1)\)\((a+1,b)\) 中至少一个点有边,所以就出现矛盾了。

得到这个结论后跑一遍缩点和拓扑排序即可,时间复杂度是 \(O(nm+q)\)

对于这类特殊图上的 adhoc 问题比如这种可达性统计,可以猜测能到达的点构成比较特别的形状,不要将其想得太过复杂。

代码
#include <bits/stdc++.h>

// #define int int64_t

const int kMaxN = 1.5e3 + 5, kMaxT = kMaxN * kMaxN;

struct Node {
  int x1, y1, x2, y2;
  Node(int _x1 = 1e9, int _y1 = 1e9, int _x2 = 0, int _y2 = 0) : x1(_x1), y1(_y1), x2(_x2), y2(_y2) {}
  friend Node operator +(Node a, Node b) {
    return {std::min(a.x1, b.x1), std::min(a.y1, b.y1), std::max(a.x2, b.x2), std::max(a.y2, b.y2)};
  }
} f[kMaxT];

int n, m, q, seed;
int scc_cnt, dfn[kMaxT], low[kMaxT], bel[kMaxT], deg[kMaxT];
std::string str[kMaxN];
std::vector<int> G[kMaxT], T[kMaxT], scc[kMaxT];
std::mt19937_64 R;

inline void init(int seed) { R = std::mt19937_64(seed); }
inline int get(int l, int r) {
  std::uniform_int_distribution<int> distribution(l, r);
  return distribution(R);
}

int getid(int x, int y) { return (x - 1) * m + y; }

void dfs(int u) {
  static int dfn_cnt = 0;
  static bool ins[kMaxT] = {0};
  static std::vector<int> stk;
  dfn[u] = low[u] = ++dfn_cnt, stk.emplace_back(u), ins[u] = 1;
  for (auto v : G[u]) {
    if (!dfn[v]) {
      dfs(v), low[u] = std::min(low[u], low[v]);
    } else if (ins[v]) {
      low[u] = std::min(low[u], dfn[v]);
    }
  }
  if (dfn[u] == low[u]) {
    ++scc_cnt;
    for (; !stk.empty();) {
      int t = stk.back(); stk.pop_back();
      f[scc_cnt] = f[scc_cnt] + Node((t - 1) / m + 1, (t - 1) % m + 1, (t - 1) / m + 1, (t - 1) % m + 1);
      ins[t] = 0, scc[bel[t] = scc_cnt].emplace_back(t);
      if (t == u) break;
    }
  }
}

void getscc() {
  for (int i = 1; i <= n * m; ++i)
    if (!dfn[i])
      dfs(i);
  for (int i = 1; i <= n * m; ++i) {
    for (auto j : G[i]) {
      if (bel[i] != bel[j]) T[bel[i]].emplace_back(bel[j]), ++deg[bel[j]];
    }
  }
}

void topo() {
  std::queue<int> q;
  for (int i = 1; i <= scc_cnt; ++i)
    if (!deg[i])
      q.emplace(i);
  for (; !q.empty();) {
    int u = q.front(); q.pop();
    for (auto v : T[u]) {
      f[v] = f[v] + f[u];
      if (!--deg[v]) q.emplace(v);
    }
  }
}

void dickdreamer() {
  std::cin >> n >> m >> q >> seed; init(seed);
  for (int i = 1; i <= n; ++i) {
    std::cin >> str[i];
    str[i] = " " + str[i];
    for (int j = 1; j <= m; ++j) {
      if (i > 1 && str[i][j] != 'U') G[getid(i, j)].emplace_back(getid(i - 1, j));
      if (i < n && str[i][j] != 'D') G[getid(i, j)].emplace_back(getid(i + 1, j));
      if (j > 1 && str[i][j] != 'L') G[getid(i, j)].emplace_back(getid(i, j - 1));
      if (j < m && str[i][j] != 'R') G[getid(i, j)].emplace_back(getid(i, j + 1));
    }
  }
  getscc(), topo();
  for (int i = 1; i <= q; ++i) {
    int a = get(1, n), b = get(1, m), c = get(1, n), d = get(1, m);
    auto [e, f, g, h] = ::f[bel[getid(c, d)]];
    assert(e <= g && f <= h);
    std::cout << (e <= a && a <= g && f <= b && b <= h);
  }
}

int32_t main() {
#ifdef ORZXKR
  freopen("in.txt", "r", stdin);
  freopen("out.txt", "w", stdout);
#endif
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  int T = 1;
  // std::cin >> T;
  while (T--) dickdreamer();
  // std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
  return 0;
}

qoj6344. The Best Problem of 2021(线性基,容斥,dp,q-analog)

下面默认可以选 \(0\),最后将答案除以二就行了。

首先求出给定 \(n\) 个数的线性基 \(B\),如果这些数的线性基大小不是 \(n\) 则答案是 \(0\),否则再进行一次消元,使得每个主元位只有恰好 \(1\)\(1\)

由于现在每一位只有恰好一个 \(1\),所以在这个线性基上从高位到低位异或主元的时候,假设枚举到了第 \(i\) 位,则目前第 \(i\) 位一定是 \(0\),那么异或了 \(i\) 对应的主元一定比不异或更大。所以 \(\text{span}(B)\cap\{0,1,\ldots,X\}\)\(\text{span}(B)\) 这个线性空间上一定是 \(\{0,1,\ldots,\text{Rank}(X)\}\)

下面让 \(X\leftarrow\text{Rank}(X)\),求 \(\text{Rank}(X)\) 可以直接从高位到低位贪心。那么问题变为求 \(\{0,1,\ldots,X\}\) 有多少个子集线性基大小是 \(n\)。首先有一个问题是一个集合的线性基并不唯一,由于最简基是唯一的,所以这个问题可以通过计数最简基来解决。

直接计数看起来还是很复杂,不妨先考虑 \(X=2^n-1\) 的情况。容易想到容斥,先钦定一个最简基能够表示当前子集,最后再通过设计容斥系数来把实际线性基大小不是 \(n\) 的方案数给消掉。设 \(h_{i,j}\) 表示考虑了最低的 \(i\) 位,选出 \(j\) 个最简基的方案数,转移式为 \(h_{i,j}\leftarrow h_{i-1,j}+h_{i-1,j-1}\cdot 2^{i-j}\)。如果钦定的最简基大小是 \(i\),则这么钦定的方案数 \(cnt_i=h_{n,i}\cdot 2^i\),要让 \(\sum cnt_i\cdot coef_i\) 是答案。

考虑怎么求出 \(coef_i\)。首先 \(coef_n=1\),实际线性基大小为 \(n\) 的显然只需要计算一次。对于其它 \(coef_i\),则枚举 \(i<j\)\(j\),需要计算出所有线性基大小恰好为 \(i\) 的子集在钦定线性基大小为 \(j\) 的方案数中被计数的次数。由于目前选择的 \(i\) 个主元位不能再有 \(1\) 了,所以容易想到把这 \(i\) 行和 \(i\) 列删掉,被计数的次数就是 \(h_{n-i,j-i}\)。这个时候会有一个问题是已经选好的 \(i\) 个主元在剩下的 \(n-i\) 位选出来的主元位是 \(1\),就不是最简基了。实际上我们不需要考虑这个问题,因为可以构造 \(h_{n-i,j-i}\) 和实际大小为 \(j\) 的合法最简基之间的双射:

  1. \(h_{n-i,j-i}\) 到实际大小为 \(j\) 的合法最简基:现在可能会有一些主元位有多于 \(1\)\(1\),这些主元位一定在新选的 \(j-i\) 个主元中,且多出来的 \(1\) 一定在初始的 \(i\) 个主元中。枚举每个初始主元,从高位到低位把多出来的位消掉即可。
  2. 实际大小为 \(j\) 的合法最简基到 \(h_{n-i,j-i}\):现在会出现一些初始主元对应的行和初始主元不同,从低位到高位复原即可。

那么可以得到 \(coef_i=-\sum_{j>i}coef_j\cdot h_{n-i,j-i}\),经过打表会发现 \(coef_i=(-1)^{n-i}2^{\binom{n-i}{2}}\),听说这个可以通过 q-analog 理论直接推出来。最终的答案就是 \(\sum h_{n,i}\cdot 2^i\cdot (-1)^{n-i}\cdot 2^{\binom{n-i}{2}}\)


再来考虑一般情况。现在需要对于每个 \(i\),计算出所有大小为 \(i\) 的最简基 \(B\)\(2^{|\text{span}(B)\cap\{0,1,\ldots,X\}|}\) 之和。先考虑一个求 \(\text{span}(B)\cap\{0,1,\ldots,X\}\) 的贪心方式:从高位到低位枚举(默认 \(1\) 是最高位,\(n\) 是最低位),设 \(now\) 表示目前选择的基的异或和,\(C\) 表示目前已经确定在 \(\{0,1,\ldots,X\}\) 的元素数量。假设枚举到了 \(i\),然后进行一些分讨:

  1. \(i\) 有基:此时 \(now_i\) 一定为 \(0\)
    1. \(X_i=0\):无事发生。
    2. \(X_i=1\)\(C\leftarrow C+2^{i+1\sim n 中主元的数量}\),再让 \(now\leftarrow now\oplus B_i\)
  2. \(i\) 无基:
    1. \(now_i=X_i\):无事发生。
    2. \(now_i=1,X_i=0\):结束,最终的元素数量是 \(C\)
    3. \(now_i=0,X_i=1\):结束,最终的元素数量是 \(C\leftarrow C+2^{i+1\sim n 中主元的数量}\)

如果到最后了还没结束,说明 \(X\) 也在 \(\text{span}(B)\) 中,元素数量为 \(C+1\)

考虑对上面的贪心方式设计 dp,由于转移需要涉及到 \(i+1\sim n\) 中基的数量,只能从低位到高位转移。下面的部分钦定最高位一定要有主元,因为这样的话主元随便选的情况下,每个非主元位是 \(0/1\) 的概率均为 \(\frac{1}{2}\)。最高位没有主元时,最终的 \(C\) 一定是 \(2^{|B|}\),方案数容易通过 \(h_{n,*}\) 计算。

\(f_{i,j}\) 表示考虑了 \(i\sim n\) 这些位,目前选了 \(j\) 个主元,且钦定 \(i-1\) 在最终 \(now\)\(X\) 的 LCP 上的 \(2^{C}\) 之和;\(g_{i,j}\) 表示考虑了 \(i\sim n\) 这些位,目前选了 \(j\) 个主元,且钦定 \(i-1\) 不在最终 \(now\)\(X\) 的 LCP 上的 \(2^{C}\) 之和。先讨论 \(g_{i+1,j}\) 的转移:

  1. \(i-1\) 还是不在 LCP 上:
    1. \(i\) 不是主元(\(i\neq 1\)):\(g_{i,j}\leftarrow g_{i+1,j}\)
    2. \(i\) 是主元:\(g_{i,j+1}\leftarrow g_{i+1,j}\cdot 2^{n-i-j}\)
  2. \(i-1\) 在 LCP 上:
    1. \(i\) 不是主元(\(i\neq 1\)):
      1. \(X_i=0,now_i=1\)\(f_{i,j}\leftarrow \frac{1}{2}\cdot g_{i+1,j}\)
      2. \(X_i=1,now_i=0\)\(f_{i,j}\leftarrow\frac{1}{2}\cdot 2^{2^j}\cdot g_{i+1,j}\)
    2. \(i\) 是主元:不可能。

再讨论 \(f_{i+1,j}\) 的转移:

  1. \(i\) 不是主元(\(i\neq 1\)):\(f_{i,j}\leftarrow\frac{1}{2}\cdot f_{i+1,j}\)
  2. \(i\) 是主元:
    1. \(X_i=0\)\(f_{i,j+1}\leftarrow 2^{n-i-j}\cdot f_{i+1,j}\)
    2. \(X_i=1\)\(f_{i,j+1}\leftarrow 2^{n-i-j}\cdot 2^{2^j}\cdot f_{i+1,j}\)

初始值是 \(f_{n+1,0}=2,g_{n+1,0}=1\),这是因为 \(n\) 还在 LCP 上说明 \(X\in\text{span}(B)\),初始 \(C\) 要设成 \(1\)。最终的 \(cnt_i\) 就是 \(f_{1,i}+h_{n-1,i}\cdot 2^{2^i}\)

总的时间复杂度是 \(O(\frac{n^3}{w})\),瓶颈在求线性基的部分。

对于这类复杂线性基题,可以先把线性基都消成每个主元位只有恰好一个 \(1\) 的最简基,反正没有任何影响,消完后在这个基中求一个数的秩就能贪心做了。

代码
#include <bits/stdc++.h>

// #define int int64_t

const int kMaxN = 2e3 + 5, kMod = 998244353, kInv2 = (kMod + 1) / 2;

int n, m;
int f[kMaxN][kMaxN], g[kMaxN][kMaxN], h[kMaxN][kMaxN], pw[kMaxN], pw2[kMaxN], cnt[kMaxN];
std::bitset<kMaxN> basis[kMaxN], lim;

int qpow(int bs, int64_t idx = kMod - 2, int mod = kMod) {
  int ret = 1;
  for (; idx; idx >>= 1, bs = (int64_t)bs * bs % kMod)
    if (idx & 1)
      ret = (int64_t)ret * bs % kMod;
  return ret;
}

inline int add(int x, int y) { return (x + y >= kMod ? x + y - kMod : x + y); }
inline int sub(int x, int y) { return (x >= y ? x - y : x - y + kMod); }
inline void inc(int &x, int y) { (x += y) >= kMod ? x -= kMod : x; }
inline void dec(int &x, int y) { (x -= y) < 0 ? x += kMod : x; }
inline int getop(int x) { return (~x & 1) ? 1 : (kMod - 1); }

void prework(int n = 2000) {
  pw[0] = 1, pw2[0] = 2;
  for (int i = 1; i <= n; ++i) {
    pw[i] = 2ll * pw[i - 1] % kMod;
    pw2[i] = 1ll * pw2[i - 1] * pw2[i - 1] % kMod;
  }
  h[0][0] = 1;
  for (int i = 1; i <= n; ++i) {
    for (int j = 0; j <= i; ++j) {
      h[i][j] = h[i - 1][j];
      if (j) inc(h[i][j], 1ll * pw[i - j] * h[i - 1][j - 1] % kMod);
    }
  }
}

bool ins(std::bitset<kMaxN> &x) {
  for (int i = 1; i <= m; ++i) {
    if (x[i]) {
      if (basis[i][i]) x ^= basis[i];
      else { basis[i] = x; return 1; }
    }
  }
  return 0;
}

void fixbasis() {
  for (int i = m; i; --i) {
    if (basis[i][i]) {
      for (int j = 1; j < i; ++j)
        if (basis[j][i])
          basis[j] ^= basis[i];
    }
  }
}

bool check(std::bitset<kMaxN> &a, std::bitset<kMaxN> &b) {
  for (int i = 1; i <= m; ++i) {
    if (a[i] < b[i]) return 1;
    if (a[i] > b[i]) return 0;
  }
  return 1;
}

void fixlim() {
  static std::bitset<kMaxN> now, tmp;
  now.reset(), tmp.reset();
  int cnt = 0;
  for (int i = 1; i <= m; ++i) {
    if (basis[i][i]) {
      ++cnt;
      now ^= basis[i], tmp[cnt] = 1;
      if (!check(now, lim)) now ^= basis[i], tmp[cnt] = 0;
    }
  }
  lim = tmp;
  assert(cnt == n);
}

void dickdreamer() {
  std::cin >> n >> m; prework();
  for (int i = 1; i <= n; ++i) {
    static std::bitset<kMaxN> tmp;
    std::string str;
    std::cin >> str;
    tmp.reset();
    for (int j = 1; j <= m; ++j) tmp[j] = str[j - 1] - '0';
    if (!ins(tmp)) return void(std::cout << "0\n");
  }
  std::string str;
  std::cin >> str;
  for (int i = 1; i <= m; ++i) lim[i] = str[i - 1] - '0';
  fixbasis(), fixlim();
  if (lim[1] == 0) return void(std::cout << "0\n");
  // for (int i = 1; i <= n; ++i) std::cerr << lim[i];
  // std::cerr << '\n';
  f[n + 1][0] = 2, g[n + 1][0] = 1;
  for (int i = n; i; --i) {
    for (int j = 0; j <= n; ++j) {
      if (!f[i + 1][j] && !g[i + 1][j]) continue;
      assert(n - i - j >= 0);
      if (i != 1) inc(g[i][j], g[i + 1][j]);
      inc(g[i][j + 1], 1ll * pw[n - i - j] * g[i + 1][j] % kMod);
      //
      if (i != 1) {
        if (lim[i] == 0) inc(f[i][j], 1ll * kInv2 * g[i + 1][j] % kMod);
        else inc(f[i][j], 1ll * kInv2 * pw2[j] % kMod * g[i + 1][j] % kMod);
      }
      //
      if (i != 1) inc(f[i][j], 1ll * kInv2 * f[i + 1][j] % kMod);
      if (lim[i] == 0) inc(f[i][j + 1], 1ll * pw[n - i - j] * f[i + 1][j] % kMod);
      else inc(f[i][j + 1], 1ll * pw[n - i - j] * pw2[j] % kMod * f[i + 1][j] % kMod);
    }
  }
  cnt[0] = 2;
  for (int i = 1; i <= n; ++i) {
    cnt[i] = add(f[1][i], 1ll * pw2[i] * h[n - 1][i] % kMod);
    // std::cerr << f[1][i] << " \n"[i == n];
  }
  int ans = 0;
  for (int i = 0; i <= n; ++i) inc(ans, 1ll * getop(n - i) * qpow(2, (n - i) * (n - i - 1) / 2) % kMod * cnt[i] % kMod);
  std::cout << 1ll * kInv2 * ans % kMod << '\n';
}

int32_t main() {
#ifdef ORZXKR
  freopen("in.txt", "r", stdin);
  freopen("out.txt", "w", stdout);
#endif
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  int T = 1;
  // std::cin >> T;
  while (T--) dickdreamer();
  // std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
  return 0;
}

P9986. [Ynoi2079] r2pspc(数据结构,分块,值域分块,只有单点加的二进制加法具有均摊)

首先可以将 \(a_i\) 离散化,实际用到的值只有不超过 \(n\) 种。容易想到直接莫队,用线段树维护二进制加法,时间复杂度是 \(O(n\sqrt m\log n)\),过不了。

最终的复杂度肯定是要优化成 \(O(n\sqrt m)\) 的,这意味着我们不能用线段树维护二进制加法。这里有个非常好的性质是只有单点加的二进制加法,维护每一位的值,每次暴力枚举找到修改位 \(x\) 后第一个 \(0\) 再暴力做修改是能够均摊分析成线性的!为了用到这个性质,在莫队的过程中就不能做减法了,只能回滚莫队。

设序列分块的块长是 \(B\),枚举询问左端点对应的块 \(p\),再从左往右拓展右端点 \(r\),暴力维护 \([R_p+1,r]\) 的区间和,这是能够均摊的。但是询问时拓展左端点到 \(l\) 再撤销就不能直接暴力了,这部分不具备均摊的性质。注意到每次拓展 \(l\) 时只会修改 \([L_p,R_p]\) 内的元素,只有 \(O(B)\) 种。这启发我们再对值域分块,将值域分成 \(O(B)\) 个块,使得 \([L_p,R_p]\) 内的元素一定是某个块的开头。这样拓展 \(l\) 的修改可以看成向 \(a_i\) 所在块的进位,那么只要对于每个块,对每个 \(v\) 能够 \(O(1)\) 求出上一个块向这个块的进位是 \(v\) 时这个块的 popcount,以及向下一个块的进位数即可。

但是会有一个问题是上一个块向当前块 \([l,r]\) 的进位数 \(v\) 不一定是 \(1\),导致块内部的进位难以维护。注意到 \(v\) 不会超过 \(n\),那么到 \(l+\log n\) 位的进位时就一定不超过 \(1\) 了。所以我们可以对于 \([l,l+\log n-1]\) 维护出一个数 \(s_i\) 表示这个区间内的位构成的二进制数,再维护 \(p_i\)\(cnt_i\) 分别表示 \([l+\log n,r]\) 的第一个 \(0\) 的位置以及区间内 \(1\) 的数量即可。每次拓展左端点就更新修改数所在块的进位标记,查询时从左往右扫每个块,通过 \(s_i,p_i,cnt_i\) 计算 popcount 和进位数即可。

由于拓展右端点时 \(0\)\(1\) 可能会互相变化,导致 \(p_i\) 并不能直接维护。我们可以维护出所有值为 \(0\) 的位置构成的链表,再对每个值域块 \([l,r]\),维护出 \([l,r]\) 最后一个出现的 \(0\) 以及 \([l,l+\log n-1]\) 最后一个出现的 \(0\)。那么在询问从左往右扫的过程中,就能够维护出 \([1,l+\log n-1]\) 最后一个 \(0\) 的位置,再通过一次求 next 即可得到 \(p_i\)

时间复杂度是 \(O(\frac{n^2}{B}+mB)\),取 \(B=\frac{n}{\sqrt m}\),最终的时间复杂度就是 \(O(n\sqrt m)\)

对于这类单点加的二进制加法问题,暴力维护每个位置的值是能够均摊的!

代码
#include <bits/stdc++.h>

// #define int int64_t

const int kMaxN = 2e5 + 5, kMaxM = 1e6 + 5, kMaxT = 2e3 + 5;

struct Query {
  int l, r;
} qq[kMaxM];

struct Info {
  int l, r, s, lst0, lst0_1, cnt, tag;
  // s : [l, l + len - 1] 构成的数
  // lst0 : 最大的 p,使得 val[p] = 0
  // lst0_1 : [l, l + len - 1] 中最大的 p,使得 val[p] = 0
  // cnt : [l + len, r] 的 1 之和
  // tag : 给 l 加的数
  Info(int _l = 0, int _r = 0, int _s = 0, int _lst0 = 0, int _lst0_1 = 0, int _cnt = 0, int _tag = 0) :
    l(_l), r(_r), s(_s), lst0(_lst0), lst0_1(_lst0_1), cnt(_cnt), tag(_tag) {}
} bl[kMaxN];

int n, m, V, B, len, tot;
int a[kMaxN], res[kMaxM], L[kMaxT], R[kMaxT], bel[kMaxN];
std::vector<int> qv[kMaxT][kMaxT];

inline void chkmax(int &x, int y) { x = (x > y ? x : y); }
inline void chkmin(int &x, int y) { x = (x < y ? x : y); }

void discrete() {
  static int unq[kMaxN], tmp[kMaxN];
  for (int i = 1; i <= n; ++i) tmp[i] = a[i];
  std::sort(tmp + 1, tmp + 1 + n);
  int pos = tmp[1], cnt = 0;
  for (int i = 1; i <= n || cnt;) {
    for (; i <= n && tmp[i] == pos; ++i, ++cnt) {}
    if (cnt) unq[++V] = pos, ++pos, cnt >>= 1;
    else pos = tmp[i];
  }
  std::sort(unq + 1, unq + 1 + V);
  V = std::unique(unq + 1, unq + 1 + V) - (unq + 1);
  for (int i = 1; i <= n; ++i) a[i] = std::lower_bound(unq + 1, unq + 1 + V, a[i]) - unq;
  assert(V <= n);
}

void prework() {
  tot = (n + B - 1) / B;
  assert(tot <= 2002);
  for (int i = 1; i <= tot; ++i) {
    L[i] = (i - 1) * B + 1, R[i] = std::min(i * B, n);
    for (int j = L[i]; j <= R[i]; ++j) bel[j] = i;
  }
}

int brute_force(int l, int r) {
  static int val[kMaxN] = {0};
  int ret = 0;
  std::vector<int> dv;
  for (int i = l; i <= r; ++i) {
    for (int j = a[i]; j <= V; ++j) {
      dv.emplace_back(j);
      if (val[j]) {
        val[j] = 0, --ret;
      } else {
        val[j] = 1, ++ret;
        break;
      }
    }
  }
  for (auto i : dv) val[i] = 0;
  return ret;
}

void solve(int b) {
  static int unq[kMaxN], pos[kMaxN], val[kMaxN];
  static int pre[kMaxN], nxt[kMaxN];
  static std::vector<int> qr[kMaxN];
  int t = 0;
  unq[++t] = 1;
  for (int i = L[b]; i <= R[b]; ++i) unq[++t] = a[i];
  std::sort(unq + 1, unq + 1 + t);
  t = std::unique(unq + 1, unq + 1 + t) - (unq + 1);
  for (int i = 1; i <= t; ++i) {
    int l = unq[i], r = (i == t ? V : unq[i + 1] - 1);
    bl[i] = {l, r, 0, r, std::min(l + len - 1, r), 0, 0};
    for (int j = bl[i].l; j <= bl[i].r; ++j) pos[j] = i;
  }
  for (int i = 1; i <= V; ++i) val[i] = 0, pre[i] = i - 1, nxt[i] = i + 1;
  for (int i = R[b] + 1; i <= n; ++i) qr[i].clear();
  nxt[0] = 1, pre[V + 1] = V;
  for (int i = b + 1; i <= tot; ++i) {
    for (auto id : qv[b][i]) {
      qr[qq[id].r].emplace_back(id);
    }
  }
  std::function<void(int)> upd1 = [&] (int x) {
    int y = 0;
    for (int i = x; i <= V; ++i) {
      if (!val[i]) {
        y = i, val[i] = 1;
        if (i >= bl[pos[i]].l + len) ++bl[pos[i]].cnt;
        else bl[pos[i]].s ^= (1 << (i - bl[pos[i]].l));
        break;
      } else {
        val[i] = 0;
        if (i >= bl[pos[i]].l + len) --bl[pos[i]].cnt;
        else bl[pos[i]].s ^= (1 << (i - bl[pos[i]].l));
        chkmax(bl[pos[i]].lst0, i);
        if (i <= bl[pos[i]].l + len - 1) chkmax(bl[pos[i]].lst0_1, i);
        if (i > x) pre[i] = i - 1;
        nxt[i] = i + 1;
      }
    }
    // std::cerr << "upd1 " << x << ' ' << y << '\n';
    assert(y);
    int pr = pre[y], nx = nxt[y];
    if (x == y) {
      nxt[pr] = nx, pre[nx] = pr;
    } else {
      nxt[pr] = x, pre[x] = pr;
      nxt[y - 1] = nx, pre[nx] = y - 1;
      // for (int i = x; i < y; ++i) {
      //   // chkmax(bl[pos[i]].lst0, i);
      //   // if (i <= bl[pos[i]].l + len - 1) chkmax(bl[pos[i]].lst0_1, i);
      //   if (i + 1 < y) nxt[i] = i + 1;
      //   // if (i - 1 >= x) pre[i] = i - 1;
      // }
      pr = y - 1;
    }
    if (y == bl[pos[y]].lst0) {
      if (pr >= bl[pos[y]].l) bl[pos[y]].lst0 = pr;
      else bl[pos[y]].lst0 = 0;
    }
    if (y == bl[pos[y]].lst0_1) {
      if (pr >= bl[pos[y]].l) bl[pos[y]].lst0_1 = pr;
      else bl[pos[y]].lst0_1 = 0;
    }
  };
  std::function<void(int, int)> upd2 = [&] (int x, int v) {
    assert(x == bl[pos[x]].l);
    bl[pos[x]].tag += v;
  };
  std::function<int()> query = [&] () {
    // for (int i = 1; i <= n; ++i) std::cerr << val[i] << " \n"[i == n];
    // for (int i = 1; i <= t; ++i) {
    //   std::cerr << "??? " << bl[i].l << ' ' << bl[i].r << ' ' << bl[i].s << ' ' << bl[i].lst0 << ' ' << bl[i].lst0_1 << ' ' << bl[i].cnt << '\n';
    // }
    int ret = 0, v = 0, lst0 = 0;
    for (int i = 1; i <= t; ++i) {
      v += bl[i].tag + bl[i].s;
      chkmax(lst0, bl[i].lst0_1);
      int leni = bl[i].r - bl[i].l + 1, p = nxt[lst0];
      // for (int j = bl[i].l; j <= bl[i].r; ++j) std::cerr << val[j] << ' ';
      // std::cerr << '\n';
      // std::cerr << lst0 << ' ' << p << ' ' << bl[i].l << ' ' << bl[i].r << ' ' << len << '\n';
      // for (int j = lst0 + 1; j <= p; ++j) {
      //   if (val[j] == 0) {
      //     if (j != p) assert(0);
      //   }
      // }
      assert(p > lst0);
      assert(p > std::min(bl[i].r, bl[i].l + len - 1));
      if (leni <= len) {
        int v1 = v & ((1 << leni) - 1), v2 = v >> leni;
        ret += __builtin_popcount(v1), v = v2;
      } else {
        int v1 = v & ((1 << len) - 1), v2 = v >> len;
        ret += __builtin_popcount(v1), v = v2;
        // std::cerr << "dickkk " << v1 << ' ' << v2 << '\n';
        assert(v == 0 || v == 1);
        if (v == 1) {
          if (p <= bl[i].r) ret += bl[i].cnt - (p - 1 - (bl[i].l + len - 1)) + 1, v = 0;
          else v = 1;
        } else {
          ret += bl[i].cnt;
        }
      }
      // std::cerr << "fuck " << ret << '\n';
      chkmax(lst0, bl[i].lst0);
    }
    assert(!v);
    return ret;
  };
  // std::cerr << "??? " << b << ' ' << L[b] << ' ' << R[b] << '\n';
  for (int i = R[b] + 1; i <= n; ++i) {
    upd1(a[i]);
    // std::cerr << i << ' ' << a[i] << '\n';
    for (auto id : qr[i]) {
      // std::cerr << "???? " << id << '\n';
      for (int j = R[b]; j >= qq[id].l; --j) upd2(a[j], 1);
      res[id] = query();
      for (int j = R[b]; j >= qq[id].l; --j) upd2(a[j], -1);
    }
  }
}

void dickdreamer() {
  std::cin >> n >> m; B = std::max<int>(1, n / sqrtl(m));
  B *= 2;
  // B = 1;
  for (int i = 1; i <= n; ++i) std::cin >> a[i];
  discrete(), prework();
  // for (int i = 1; i <= n; ++i) std::cerr << a[i] << " \n"[i == n];
  len = std::__lg(n) + 1;
  for (int i = 1; i <= m; ++i) {
    std::cin >> qq[i].l >> qq[i].r;
    // std::cout << brute_force(qq[i].l, qq[i].r) << '\n';
    if (bel[qq[i].l] == bel[qq[i].r]) res[i] = brute_force(qq[i].l, qq[i].r);
    else qv[bel[qq[i].l]][bel[qq[i].r]].emplace_back(i);
  }
  for (int i = 1; i < tot; ++i) solve(i);
  for (int i = 1; i <= m; ++i) std::cout << res[i] << '\n';
  // for (int i = 1; i <= n; ++i) std::cerr << a[i] << " \n"[i == n];
}

int32_t main() {
#ifdef ORZXKR
  freopen("in.txt", "r", stdin);
  freopen("out.txt", "w", stdout);
#endif
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  int T = 1;
  // std::cin >> T;
  while (T--) dickdreamer();
  // std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
  return 0;
}

P14463. 【MX-S10-T4】『FeOI-4』呼吸之野(数据结构,线段树)

首先一个区间 \([l,r]\) 是关于 \(x\) 的好区间,当且仅当 \(\sum_{i=l}^{r}{(-1)^{[a_i<x]}}\geq 0\)。由于所有极短好区间左右端点都单调递增,那么如果我们能通过扫描线求出关于 \(x\) 的极短好区间总数,右端点不超过 \(r\) 的极短好区间数量以及左端点不小于 \(l\) 的极端好区间数量即可得到答案。

下面只考虑求右端点不超过 \(r\) 的极短好区间数量,即我们需要求出 \([1,r]\) 中有哪些位置可以作为极短好区间的右端点。不妨设 \(a(i,x)=(-1)^{[a_i<x]}\)\(S(i,x)=\sum_{j=1}^{i}{a(j,x)}\)\(f(i,x)\) 表示关于 \(x\) 且右端点为 \(i\) 的好区间中左端点的最大值,显然如果 \(f(i,x+1)\) 存在的话一定有 \(f(i,x+1)\leq f(i,x)\)

考虑对于单个 \(x\) 怎么求出可能的右端点。从左往右扫描右端点,假设扫到了 \(i\),上一个可以作为极短好区间的是 \(j\)。因为要让 \([f(i,x),i]\) 不包含 \([f(j,x),j]\),所以 \(f(i,x)\) 只能取 \([f(j,x)+1,i-k+1]\) 中满足 \(S(l,x)\leq S(i,x)\) 最大的 \(l\),如果存在则让 \(j\leftarrow i\),否则 \(i\) 一定不能作为右端点。但是这么做是难以优化的,因为找右端点的过程中还需要维护对应的左端点,放到线段树上对所有 \(x\) 同时维护时难以快速更新每个 \(x\) 对应的 \(j\) 以及 \(f(j,x)\)。我们需要对于单个 \(x\) 得到一个做法,只关心上一个可以作为右端点的 \(j\),而不关心 \(f(j,x)\)。为了解决这个问题,下面进行一些有关 \(i\)\(j\) 的分类讨论:

  1. \(S(j+1,i,x)>0\)

    \(S(f(j,x),i,x)=S(f(j,x),j,x)+S(j+1,i,x)>0\),所以 \(S(f(j,x)+1,i,x)\geq 0\)。此时 \(f(j,x)+1\) 一定可以作为 \(i\) 的左端点,这说明 \(i\) 一定可以作为右端点。

  2. \(S(j+1,i,x)\leq 0\)

    对于所有左端点 \(p\),一定有 \(S(p,j,x)\geq S(p,i,x)\),所以 \(f(i,x)\) 一定不会在 \([f(j,x)+1,j-k+1]\) 中,否则 \(f(j,x)\) 取到这个数一定更优。由于我们只需要判断 \([f(i,x),i]\) 是否能够不包含 \([f(j,x),j]\) 就行了,所以只需要在 \([j-k+2,i-k+1]\) 中找左端点即可,如果有则说明 \(i\) 可以作为极短好区间的右端点,否则一定不行。

同时容易发现一个性质:如果任意 \(j\) 满足 \([f(j,x),j]\subseteq[f(i,x),i]\)\(f(i,x+1)\) 存在,则 \([f(j,x+1),j]\subseteq [f(i,x+1),i]\)

证明 考虑从小到大扫 $x$ 的过程,每次会将 $pos_x$ 位置的值从 $1$ 变成 $-1$,所以 $f(i,x+1)\leq f(i,x)$,再进行分类讨论:
  1. 如果 \(pos_{x+1}\notin [f(j,x),j]\),则 \(f(j,x)\) 不会变,所以 \(f(i,x+1)\leq f(i,x)\leq f(j,x)\),仍然满足包含关系。
  2. 如果 \(pos_{x+1}\in[f(j,x),j]\)
    1. \(f(j,x)=f(i,x)\),则 \(f(j,x+1)=f(i,x+1)\),满足条件。
    2. \(f(i,x)<f(j,x)\),则 \(S(f(i,x),f(j,x)-1,x)>0\),否则 \(f(i,x)\) 就等于 \(f(j,x)\) 了,这说明 \(S(f(i,x+1),f(j,x)-1,x+1)=S(f(i,x+1),f(i,x)-1,x+1)+S(f(i,x),f(j,x)-1,x+1)\geq 2\),由于 \(S(f(j,x),j,x+1)\) 相比 \(S(f(j,x),j,x)\) 只会变化至多 \(2\),所以 \(f(i,x+1)\leq f(j,x+1)\),满足条件。

根据上面的性质,每个能让 \(i\) 作为右端点的 \(x\) 一定构成一个前缀,我们只需要求出这个最大的前缀就行了。然后考虑对所有 \(x\) 一起做,设 \(A(i,x)=S(i,x)-\min_{l\in[\max(j_x-k+1,0),i-k]}S(l,x)\)\(B(i,x)=S(i,x)-S(j_x,x)\)\(C(i,x)=S(i,x)-S(i-k,x)\)。我们从 \(k\) 开始枚举右端点 \(i\),初始的 \(A(i,x),B(i,x),C(i,x)\) 都可以预处理出来。每次让右端点从 \(i-1\) 移动到 \(i\) 时,三个数组的变化就是:

  1. \(A(i,x)\leftarrow A(i-1,x)+a(i,x)\)
  2. \(B(i,x)\leftarrow B(i-1,x)+a(i,x)\)
  3. \(C(i,x)\leftarrow C(i-1,x)+a(i,x)-a(i-k,x)\)
  4. \(A(i,x)\leftarrow \max(A(i,x),C(i,x))\)

\(x\)\(i\) 可以作为右端点的条件是 \(A(i,x)\geq 0\) 或者 \(C(i,x)>0\),对这些 \(x\) 还要让 \(j_x\leftarrow i,A(i,x)\leftarrow -\infty,B(i,x)\leftarrow 0\)。所有操作都能用一个线段树维护,找 \(x\) 就在线段树上二分即可(具体细节见代码)。

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

代码
#include <bits/stdc++.h>

#define int int64_t

const int kMaxN = 1e5 + 5;

struct Query {
  int l, r, x;
} qq[kMaxN];

int n, k, q;
int a[kMaxN], lim1[kMaxN], lim2[kMaxN], cnt[kMaxN], both[kMaxN];
std::vector<int> qvl[kMaxN], qvr[kMaxN];

struct Tag {
  int mxca, sumc, suma, kb, bb;
  Tag(int _mxca = -1e9, int _sumc = 0, int _suma = 0, int _kb = 1, int _bb = 0) :
    mxca(_mxca), sumc(_sumc), suma(_suma), kb(_kb), bb(_bb) {}
  bool clear() { return mxca == -1e9 && !sumc && !suma && kb == 1 && !bb; }
  friend Tag operator +(Tag a, Tag b) {
    if (a.clear()) return b;
    if (b.clear()) return a;
    static Tag ret;
    ret.mxca = std::max(a.mxca + b.suma, a.sumc + b.mxca);
    ret.sumc = a.sumc + b.sumc;
    ret.suma = a.suma + b.suma;
    ret.kb = a.kb * b.kb;
    ret.bb = a.bb * b.kb + b.bb;
    return ret;
  }
};

struct Node {
  int mxca, sumc, suma, sumb;
  Node(int _mxca = 0, int _sumc = 0, int _suma = 0, int _sumb = 0) :
    mxca(_mxca), sumc(_sumc), suma(_suma), sumb(_sumb) {}
  friend Node operator +(Node a, Tag b) {
    static Node ret;
    ret.mxca = std::max(a.mxca + b.suma, a.sumc + b.mxca);
    ret.sumc = a.sumc + b.sumc;
    ret.suma = a.suma + b.suma;
    ret.sumb = a.sumb * b.kb + b.bb;
    return ret;
  }
  friend Node operator +(Node a, Node b) {
    static Node ret;
    ret.mxca = std::max(a.mxca, b.mxca);
    ret.sumc = std::max(a.sumc, b.sumc);
    ret.suma = std::max(a.suma, b.suma);
    ret.sumb = std::max(a.sumb, b.sumb);
    return ret;
  }
};

struct SGT {
  Node t[kMaxN * 4]; Tag tag[kMaxN * 4];
  void pushup(int x) { t[x] = t[x << 1] + t[x << 1 | 1]; }
  void addtag(int x, Tag v) { t[x] = t[x] + v, tag[x] = tag[x] + v; }
  void pushdown(int x) {
    if (!tag[x].clear()) addtag(x << 1, tag[x]), addtag(x << 1 | 1, tag[x]), tag[x] = Tag();
  }
  void build(int x, int l, int r, int *a) {
    tag[x] = Tag();
    if (l == r) return void(t[x] = {a[l], a[l], 0, a[l]});
    int mid = (l + r) >> 1;
    build(x << 1, l, mid, a), build(x << 1 | 1, mid + 1, r, a);
    pushup(x);
  }
  void update(int x, int l, int r, int ql, int qr, Tag v) {
    if (l > qr || r < ql) return;
    else if (l >= ql && r <= qr) return addtag(x, v);
    pushdown(x);
    int mid = (l + r) >> 1;
    update(x << 1, l, mid, ql, qr, v), update(x << 1 | 1, mid + 1, r, ql, qr, v);
    pushup(x);
  }
  int getpos(int x, int l, int r) { // 第一个 >= 0 的位置
    if (t[x].mxca < 0 && t[x].sumb <= 0) return 0;
    if (l == r) return l;
    pushdown(x);
    int mid = (l + r) >> 1;
    if (t[x << 1 | 1].mxca >= 0 || t[x << 1 | 1].sumb > 0) return getpos(x << 1 | 1, mid + 1, r);
    else return getpos(x << 1, l, mid);
  }
} sgt;

struct BIT {
  int c[kMaxN];
  void clear() { std::fill_n(c + 1, n, 0); }
  void upd(int x, int v) {
    for (; x <= n; x += x & -x) c[x] += v;
  }
  int qry(int x) {
    int ret = 0;
    for (; x; x -= x & -x) ret += c[x];
    return ret;
  }
} bit;

void getlim1() {
  static int val[kMaxN];
  std::fill_n(val + 1, n, 0);
  std::fill_n(lim1 + 1, n, 0);
  std::fill_n(cnt + 1, n, 0);
  for (int i = 1; i <= k; ++i) ++val[1], val[a[i] + 1] -= 2;
  for (int i = 1; i <= n; ++i) val[i] += val[i - 1];
  sgt.build(1, 1, n, val);
  bit.clear();
  for (int i = k; i <= n; ++i) {
    if (i >= k + 1) {
      if (a[i] > a[i - k]) {
        // A[x] += 1, C[x] += 0, A[x] = max(A[x], C[x])
        // A(1), C(0), A(0)
        sgt.update(1, 1, n, 1, a[i - k], {0, 0, 1, 1, 1});
        // A[x] += 1, C[x] += 2, A[x] = max(A[x], C[x])
        // A(1), C(2), A(0)
        sgt.update(1, 1, n, a[i - k] + 1, a[i], {2, 2, 1, 1, 1});
        // A[x] += -1, C[x] += 0, A[x] = max(A[x], C[x])
        // A(-1), C(0), A(0)
        sgt.update(1, 1, n, a[i] + 1, n, {0, 0, -1, 1, -1});
      } else {
        // A[x] += 1, C[x] += 0, A[x] = max(A[x], C[x])
        sgt.update(1, 1, n, 1, a[i], {0, 0, 1, 1, 1});
        // A[x] += -1, C[x] += -2, A[x] = max(A[x], C[x])
        sgt.update(1, 1, n, a[i] + 1, a[i - k], {-2, -2, -1, 1, -1});
        // A[x] += -1, C[x] += 0, A[x] = max(A[x], C[x])
        sgt.update(1, 1, n, a[i - k] + 1, n, {0, 0, -1, 1, -1});
      }
    }
    lim1[i] = sgt.getpos(1, 1, n);
    assert(lim1[i]);
    // A(-1e9)
    sgt.update(1, 1, n, 1, lim1[i], {-(int)1e9, 0, -(int)1e9, 0, 0});
    ++cnt[1], --cnt[lim1[i] + 1];
    bit.upd(lim1[i], 1);
    for (auto id : qvr[i]) {
      both[id] += bit.qry(n) - bit.qry(qq[id].x - 1);
    }
    // std::cerr << i << ' ' << lim1[i] << '\n';
    // {
    //   auto tmp = sgt.query(1, 1, n, 5);
    //   std::cerr << i << " : " << tmp.
    // }
  }
  for (int i = 1; i <= n; ++i) cnt[i] += cnt[i - 1];
  // std::cerr << "??? " << cnt[5] << ' ' << both[1] << '\n';
}

void getlim2() {
  static int val[kMaxN];
  std::fill_n(val + 1, n, 0);
  std::fill_n(lim2 + 1, n, 0);
  for (int i = n - k + 1; i <= n; ++i) ++val[1], val[a[i] + 1] -= 2;
  for (int i = 1; i <= n; ++i) val[i] += val[i - 1];
  sgt.build(1, 1, n, val);
  bit.clear();
  for (int i = n - k + 1; i; --i) {
    if (i <= n - k) {
      if (a[i] > a[i + k]) {
        // A[x] += 1, C[x] += 0, A[x] = max(A[x], C[x])
        sgt.update(1, 1, n, 1, a[i + k], {0, 0, 1, 1, 1});
        // A[x] += 1, C[x] += 2, A[x] = max(A[x], C[x])
        sgt.update(1, 1, n, a[i + k] + 1, a[i], {2, 2, 1, 1, 1});
        // A[x] += -1, C[x] += 0, A[x] = max(A[x], C[x])
        sgt.update(1, 1, n, a[i] + 1, n, {0, 0, -1, 1, -1});
      } else {
        // A[x] += 1, C[x] += 0, A[x] = max(A[x], C[x])
        sgt.update(1, 1, n, 1, a[i], {0, 0, 1, 1, 1});
        // A[x] += -1, C[x] += -2, A[x] = max(A[x], C[x])
        sgt.update(1, 1, n, a[i] + 1, a[i + k], {-2, -2, -1, 1, -1});
        // A[x] += -1, C[x] += 0, A[x] = max(A[x], C[x])
        sgt.update(1, 1, n, a[i + k] + 1, n, {0, 0, -1, 1, -1});
      }
    }
    lim2[i] = sgt.getpos(1, 1, n);
    assert(lim2[i]);
    sgt.update(1, 1, n, 1, lim2[i], {-(int)1e9, 0, -(int)1e9, 0, 0});
    bit.upd(lim2[i], 1);
    for (auto id : qvl[i]) {
      both[id] += bit.qry(n) - bit.qry(qq[id].x - 1);
    }
    // std::cerr << i << ' ' << lim2[i] << '\n';
    // {
    //   auto tmp = sgt.query(1, 1, n, 5);
    //   std::cerr << i << " : " << tmp.
    // }
  }
  // for (int i = 1; i <= n; ++i) assert(bit.qry(n) - bit.qry(i - 1) == cnt[i]);
}

void dickdreamer() {
  std::cin >> n >> k >> q;
  for (int i = 0; i <= n + 1; ++i) qvl[i].clear(), qvr[i].clear();
  for (int i = 1; i <= n; ++i) std::cin >> a[i];
  for (int i = 1; i <= q; ++i) {
    std::cin >> qq[i].l >> qq[i].r >> qq[i].x;
    qvl[qq[i].l].emplace_back(i), qvr[qq[i].r].emplace_back(i);
    both[i] = 0;
  }
  getlim1(), getlim2();
  for (int i = 1; i <= q; ++i) std::cout << std::max<int>(both[i] - cnt[qq[i].x], 0) << '\n';
}

int32_t main() {
#ifdef ORZXKR
  freopen("in.txt", "r", stdin);
  freopen("out.txt", "w", stdout);
#endif
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  int T = 1;
  std::cin >> T;
  while (T--) dickdreamer();
  // std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
  return 0;
}

qoj9222. Price Combo(数据结构,贪心,找性质)

首先如果每个物品只有一维的价格,那么买的策略一定是按照 \(a_i\) 从小到大排序后选择第 \(1,3,5,\ldots,2k-1\) 大的去买。考虑把 \(b_i\) 加上,还是先将 \(a_i\) 从小到大排序,但是此时如果按照 \(a_i\) 从大往小 dp 的话是难以把 \(b_i\) 压进状态的,因为按照 \(a_i\) 排序时 \(b_i\) 是乱序的。

考虑找一些性质来让 dp 可以把 \(b_i\) 压进状态。注意到如果 \(a_i\geq a_j,b_i\leq b_j\)\(i\) 选 A、\(j\) 选 B 一定不如让 \(i\) 选 B、\(j\) 选 A,也就是说将 \((a_i,b_i)\) 放到二维平面上,不会出现左上选 B,右下选 A 的情况。那么将所有选 A 的点拿出来,一定存在一个轮廓点集 \(\{(a_i,b_i)\}\) 使得这个点集内的点左上(包括正左和正上)全都选进 A,其余全都选 B。

这样就能 dp 了,设 \(f_{i,j,0/1,0/1}\) 表示考虑了 \(a_k\geq i\) 的所有点,钦定选 A 的点中 \(b_k\) 最小为 \(j\)\(0/1\) 个选 A,在上一个钦定的轮廓点右上有 \(0/1\) 个选 B(其余目前不算进去)时目前已经钦定好的点的最优答案,每个 \(f_{i,j}\) 可以看成一个 \(2\times 2\) 的矩阵,转移时会分别对两维进行 \((\min,+)\) 卷积。从 \(f_{i+1,j}\) 转移到 \(f_{i,*}\) 时,如果 \((i,*)\) 没有轮廓点,则所有 \(a_k=i,b_k\geq j\) 的点都要选到 A 里,所有 \(a_k=i,b_k<j\) 的点都要选到 B 里,但是目前不对这个进行转移,所以只对矩阵的第一维进行转移。如果钦定 \((i,l)\) 是轮廓点,则 \(a_k=i,b_k\geq l\) 的点都要放到 A 里,且 \(a_k>i,l\leq b_k<j\) 的点就要进行转移了,这些点一定被钦定为选 B 了,所以这时会对矩阵的两维同时转移,最终转移到 \(f_{i,l}\)

具体实现则用一个线段树维护 \(f_{i,*}\) 这些矩阵,同时维护 \(B_j\) 表示 \(a_k>i,b_k=j\) 的点放到 B 中的转移矩阵。对于不是轮廓点的转移就是前缀乘上 A 矩阵,对于是轮廓点的转移,则要对 \(f_{i,l}\) 求出 \(f_{i+1,j}B_{j-1}B_{j-2}\ldots B_l\) 的最优值,同样可以用线段树找到这个矩阵。

注意这里如果有两个点重合的话上面的状态会有一些问题,因为这样设状态任意两个重合的点最终的选择都会一样,而实际不一样可能会更优,所以对于 \(b_i\) 要将其离散化成一个排列,大小相同的内部随便排序。

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

对于这类与两维有关的贪心问题,可以先想想只有一维怎么做,如果不能直接拓展到二维就要去找一些性质了。

代码
#include <bits/stdc++.h>

#define int int64_t

const int kMaxN = 2e5 + 5, kInf = 1e18;

int n, ta, tb;
int a[kMaxN], b[kMaxN], unqa[kMaxN], unqb[kMaxN];
std::vector<int> vec[kMaxN];

inline void chkmax(int &x, int y) { x = (x > y ? x : y); }
inline void chkmin(int &x, int y) { x = (x < y ? x : y); }

struct TransA {
  int a[2][2];
  TransA(int a00 = 0, int a01 = kInf, int a10 = kInf, int a11 = 0) {
    a[0][0] = a00, a[0][1] = a01, a[1][0] = a10, a[1][1] = a11;
  }
  bool clear() { return a[0][0] == 0 && a[0][1] == kInf && a[1][0] == kInf && a[1][1] == 0; }
  friend TransA operator +(TransA a, TransA b) {
    static TransA ret;
    ret.a[0][0] = ret.a[0][1] = ret.a[1][0] = ret.a[1][1] = kInf;
    for (int i = 0; i < 2; ++i)
      for (int j = 0; j < 2; ++j)
        for (int k = 0; k < 2; ++k)
          chkmin(ret.a[i][k], a.a[i][j] + b.a[j][k]);
    return ret;
  }
};

struct TransB {
  int a[2][2];
  TransB(int a00 = 0, int a01 = kInf, int a10 = kInf, int a11 = 0) {
    a[0][0] = a00, a[0][1] = a01, a[1][0] = a10, a[1][1] = a11;
  }
  bool clear() { return a[0][0] == 0 && a[0][1] == kInf && a[1][0] == kInf && a[1][1] == 0; }
  friend TransB operator +(TransB a, TransB b) {
    static TransB ret;
    ret.a[0][0] = ret.a[0][1] = ret.a[1][0] = ret.a[1][1] = kInf;
    for (int i = 0; i < 2; ++i)
      for (int j = 0; j < 2; ++j)
        for (int k = 0; k < 2; ++k)
          chkmin(ret.a[i][k], a.a[i][j] + b.a[j][k]);
    return ret;
  }
};

struct Matrix {
  int a[2][2];
  Matrix(int a00 = kInf, int a01 = kInf, int a10 = kInf, int a11 = kInf) {
    a[0][0] = a00, a[0][1] = a01, a[1][0] = a10, a[1][1] = a11;
  }
  friend Matrix operator +(Matrix a, Matrix b) {
    static Matrix ret;
    ret = Matrix();
    for (int i = 0; i < 2; ++i)
      for (int j = 0; j < 2; ++j)
        ret.a[i][j] = std::min(a.a[i][j], b.a[i][j]);
    return ret;
  }
  friend Matrix operator +(Matrix a, TransA b) {
    static Matrix ret;
    ret = Matrix();
    for (int i = 0; i < 2; ++i)
      for (int j = 0; j < 2; ++j)
        for (int k = 0; k < 2; ++k)
          chkmin(ret.a[k][j], a.a[i][j] + b.a[i][k]);
    return ret;
  }
  friend Matrix operator +(Matrix a, TransB b) {
    static Matrix ret;
    ret = Matrix();
    for (int i = 0; i < 2; ++i)
      for (int j = 0; j < 2; ++j)
        for (int k = 0; k < 2; ++k)
          chkmin(ret.a[i][k], a.a[i][j] + b.a[j][k]);
    return ret;
  }
};

struct Node {
  Matrix miba;
  TransB trb;
  Node(Matrix _miba = Matrix(), TransB _trb = TransB()) : miba(_miba), trb(_trb) {}
  friend Node operator +(Node a, Node b) {
    return {a.miba + (b.miba + a.trb), b.trb + a.trb};
  }
};

struct SGT {
  Node t[kMaxN * 4];
  TransA taga[kMaxN * 4];
  void pushup(int x) {
    t[x] = t[x << 1] + t[x << 1 | 1];
  }
  void addtaga(int x, TransA v) { t[x].miba = t[x].miba + v, taga[x] = taga[x] + v; }
  void pushdown(int x) {
    if (!taga[x].clear()) addtaga(x << 1, taga[x]), addtaga(x << 1 | 1, taga[x]), taga[x] = TransA();
  }
  void build(int x, int l, int r) {
    t[x] = Node(), taga[x] = TransA();
    if (l == r) return;
    int mid = (l + r) >> 1;
    build(x << 1, l, mid), build(x << 1 | 1, mid + 1, r);
  }
  void updatea(int x, int l, int r, int ql, int qr, TransA v) {
    if (l > qr || r < ql) return;
    else if (l >= ql && r <= qr) return addtaga(x, v);
    pushdown(x);
    int mid = (l + r) >> 1;
    updatea(x << 1, l, mid, ql, qr, v), updatea(x << 1 | 1, mid + 1, r, ql, qr, v);
    pushup(x);
  }
  void updateb(int x, int l, int r, int ql, TransB v) {
    if (l == r) return void(t[x].trb = t[x].trb + v);
    pushdown(x);
    int mid = (l + r) >> 1;
    if (ql <= mid) updateb(x << 1, l, mid, ql, v);
    else updateb(x << 1 | 1, mid + 1, r, ql, v);
    pushup(x);
  }
  void update(int x, int l, int r, int ql, Matrix v) {
    if (l == r) return void(t[x].miba = t[x].miba + v);
    pushdown(x);
    int mid = (l + r) >> 1;
    if (ql <= mid) update(x << 1, l, mid, ql, v);
    else update(x << 1 | 1, mid + 1, r, ql, v);
    pushup(x);
  }
  Node query(int x, int l, int r, int ql, int qr) {
    if (l > qr || r < ql) return Node();
    else if (l >= ql && r <= qr) return t[x];
    pushdown(x);
    int mid = (l + r) >> 1;
    return query(x << 1, l, mid, ql, qr) + query(x << 1 | 1, mid + 1, r, ql, qr);
  }
} sgt;

void discrete(int *a, int &t, int *unq) {
  for (int i = 1; i <= n; ++i) unq[i] = a[i];
  std::sort(unq + 1, unq + 1 + n);
  t = std::unique(unq + 1, unq + 1 + n) - (unq + 1);
  for (int i = 1; i <= n; ++i) a[i] = std::lower_bound(unq + 1, unq + 1 + t, a[i]) - unq;
}

void dickdreamer() {
  std::cin >> n;
  for (int i = 1; i <= n; ++i) std::cin >> a[i];
  for (int i = 1; i <= n; ++i) std::cin >> b[i];
  discrete(a, ta, unqa), discrete(b, tb, unqb);
  static int buc[kMaxN], real[kMaxN];
  std::fill_n(buc, tb + 1, 0);
  for (int i = 1; i <= n; ++i) ++buc[b[i]];
  for (int i = 1; i <= tb; ++i) buc[i] += buc[i - 1], real[i] = unqb[i];
  for (int i = 1; i <= n; ++i) {
    int val = real[b[i]];
    unqb[b[i] = buc[b[i]]--] = val;
  }
  tb = n;
  for (int i = 1; i <= n; ++i) vec[a[i]].emplace_back(b[i]);
  sgt.build(1, 1, tb + 1);
  sgt.update(1, 1, tb + 1, tb + 1, {0, kInf, kInf, kInf});
  for (int i = ta; i; --i) {
    std::sort(vec[i].begin(), vec[i].end());
    for (auto j : vec[i]) {
      // auto [miba, trb] = sgt.query(1, 1, tb + 1, j, tb + 1);
      // if (i == 1) std::cerr << "!!! " << j << " : " << miba.a[0][0] << ' ' << miba.a[0][1] << ' ' << miba.a[1][0] << ' ' << miba.a[1][1] << '\n';
      // std::cerr << j << ' ' << mia.a[0][0] << ' ' << mia.a[1][0] << ' ' << miba.a[0][0] << ' ' << miba.a[1][0] << '\n';
      sgt.update(1, 1, tb + 1, j, sgt.query(1, 1, tb + 1, j, tb + 1).miba);
    }
    for (auto j : vec[i]) {
      // if (i == 1) std::cerr << "dick " << unqa[i] << '\n';
      sgt.updatea(1, 1, tb + 1, 1, j, {kInf, unqa[i], 0, kInf});
    }
    // for (auto j : vec[i]) {
    //   auto [miba, trb] = sgt.query(1, 1, tb + 1, j, j);
    //   // if (i == 1) std::cerr << "??? " << j << " : " << miba.a[0][0] << ' ' << miba.a[0][1] << ' ' << miba.a[1][0] << ' ' << miba.a[1][1] << '\n';
    //   // std::cerr << j << ' ' << mia.a[0][0] << ' ' << mia.a[1][0] << ' ' << miba.a[0][0] << ' ' << miba.a[1][0] << '\n';
    // }
    for (auto j : vec[i]) sgt.updateb(1, 1, tb + 1, j, {kInf, unqb[j], 0, kInf});
  }
  auto res = sgt.query(1, 1, tb + 1, 1, tb + 1).miba;
  // auto trb = sgt.query(1, 1, tb + 1, 1, 1).trb;
  // std::cerr << "shabi " << res.a[0][0] << ' ' << res.a[0][1] << ' ' << res.a[1][0] << ' ' << res.a[1][1] << '\n';
  // std::cerr << "shabi-trb " << trb.a[0][0] << ' ' << trb.a[0][1] << ' ' << trb.a[1][0] << ' ' << trb.a[1][1] << '\n';
  std::cout << std::min({res.a[0][0], res.a[0][1], res.a[1][0], res.a[1][1]}) << '\n';
}

int32_t main() {
#ifdef ORZXKR
  freopen("in.txt", "r", stdin);
  freopen("out.txt", "w", stdout);
#endif
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  int T = 1;
  // std::cin >> T;
  while (T--) dickdreamer();
  // std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
  return 0;
}

P5861 [IOI 2015] teams(数据结构,贪心,单调栈,主席树,匹配可行性判断)

首先对于单组询问,容易得到一个贪心做法:将 \(k\) 从小到大排序,扫到 \(k_i\) 时将 \(l_j\in[k_{i-1}+1,k_i]\)\([l_j,r_j]\) 加到优先队列中,再把小于 \(k_i\) 的右端点弹掉,每次选择优先队列中最小的 \(k_i\) 个右端点与 \(k_i\) 进行匹配,并将这些右端点从队列中删除,如果找不到则无解。单次时间复杂度是 \(O(n\log n)\)。这个做法慢在无法利用到 \(k\) 数组的大小,每次只能 \(O(n)\) 扫一遍每个区间。

考虑利用队列中 \(\geq k_i\) 的右端点的性质来优化这个做法。如果只有 \(k_1\),那么与 \(k_1\) 匹配完,队列中剩下的右端点一定是将 \(l_j\leq k_1\leq r_j\)\(r_j\) 从小到大排序后的一段后缀。这个后缀的形式是很优美的,但是对于后面的 \(k\) 右端点就不一定是 \([1,k_i]\) 的某段后缀了,所以还需要找一些性质。设 \(h\) 是剩下右端点的最小值。现在加上 \(k_2\),如果 \(h<k_2\),则 \(k_1\) 怎么匹配和 \(k_2\) 一点关系都没有,因为 \(k_1\) 不可能匹配到能够与 \(k_2\) 匹配的区间;如果 \(h\geq k_2\),则 \(k_2\) 匹配的就是队列里剩下的与新加入的右端点中最小的 \(k_2\) 个。如图,其中的红线代表 \(h\)

从上图可知,前 \(k_2\) 小一定是先从右边红线下面的部分开始找,如果红线下面的部分就能匹配完,则剩下的右端点一定是左端点在 \([1,k_1]\) 内的一段后缀以及左端点在 \([k_1+1,k_2]\) 内的一段后缀,且 \([1,k_1]\) 剩下来的最小右端点不小于 \([k_1+1,k_2]\) 的;如果红线下面的部分不能匹配完,则剩下的右端点也是左端点在 \([1,k_2]\) 内的一段后缀!根据这个分类讨论,我们猜测一定可以将 \([1,k_i]\) 划分成若干个不交区间,左端点在每个区间内没被删掉的右端点都构成一段后缀,且这些后缀分别的最小值一定不增。

具体地,在从左往右扫 \(k\) 的过程中维护一个栈 \(s\) 表示划分的区间,\(c_i\) 表示左端点在 \([s_{i-1}+1,s_i]\) 内剩余的后缀长度,\(h_i\) 表示左端点在 \([s_{i-1}+1,s_i]\) 内剩余后缀中右端点的最小值。加入一个 \(k\) 时,先一直弹栈直到 \(h_{top}\geq k\),并记录 \(t\) 表示还需要匹配的区间数量,初始 \(t\) 设为 \(k\)

如果 \(l_j\in[s_{top}+1,k],r_j\in[k,h_{top}-1]\) 的区间个数大于等于 \(t\),则匹配的是左端点在 \([s_{top}+1,k]\) 内的一段前缀,直接将 \(k\) 加进栈里即可。否则队列里就不会再有别的右端点在 \([k,h_{top}-1]\) 这个范围内了,所以可以让 \(k\leftarrow h_{top}\),此时 \([s_{top-1}+1,s_{top}]\) 剩下的右端点与 \([s_{top}+1,k]\) 剩下的右端点拼起来一定构成 \([s_{top-1}+1,k]\) 的一段最小值是 \(h_{top}\) 的后缀,维护一下这个后缀的长度后就能把 \(s_{top}\) 删掉了,这么一直弹栈直到 \(t\) 变为零。如果把栈弹空了 \(t\) 还是大于零就无解。

具体细节见代码。

时间复杂度:\(O((n+\sum m)\log n)\)

代码
#include <bits/stdc++.h>

// #define int int64_t

const int kMaxN = 5e5 + 5, kMaxT = kMaxN * 25;

struct Node {
  int ls, rs, cnt;
} tr[kMaxT];

int n, q;
int l[kMaxN], r[kMaxN];
int sgt_cnt, rt[kMaxN];
std::vector<int> vec[kMaxN];

int update(int x, int l, int r, int ql, int v) {
  int nz = ++sgt_cnt;
  tr[nz] = tr[x], tr[nz].cnt += v;
  if (l == r) return nz;
  int mid = (l + r) >> 1;
  if (ql <= mid) tr[nz].ls = update(tr[x].ls, l, mid, ql, v);
  else tr[nz].rs = update(tr[x].rs, mid + 1, r, ql, v);
  return nz;
}

int query(int x, int y, int l, int r, int ql, int qr) {
  if (!x && !y || l > qr || r < ql) return 0;
  else if (l >= ql && r <= qr) return tr[y].cnt - tr[x].cnt;
  int mid = (l + r) >> 1;
  return query(tr[x].ls, tr[y].ls, l, mid, ql, qr) + query(tr[x].rs, tr[y].rs, mid + 1, r, ql, qr);
}

int getkth(int x, int y, int l, int r, int k) { // 求第 k 大
  if (k > tr[y].cnt - tr[x].cnt) return -1;
  else if (l == r) return l;
  int mid = (l + r) >> 1, crs = tr[tr[y].rs].cnt - tr[tr[x].rs].cnt;
  if (k <= crs) return getkth(tr[x].rs, tr[y].rs, mid + 1, r, k);
  else return getkth(tr[x].ls, tr[y].ls, l, mid, k - crs);
}

bool check(std::vector<int> vec) {
  std::sort(vec.begin(), vec.end());
  static int stk[kMaxN] = {0}, h[kMaxN] = {0}, c[kMaxN] = {0};
  int top = 0;
  h[0] = n + 1;
  for (auto k : vec) {
    for (; top && h[top] < k; --top) {}
    int las = k, nowh = k, nowc = query(rt[stk[top]], rt[k], 1, n, k, n);
    // std::cerr << "??? " << k << ' ' << nowc << '\n';
    for (; las && top >= 0;) {
      int cc = std::max(nowc - query(rt[stk[top]], rt[k], 1, n, h[top], n), 0);
      // std::cerr << "shabi " << cc << '\n';
      if (cc < las) {
        las -= cc, nowh = h[top], nowc = nowc - cc + c[top];
        --top;
      } else {
        nowc -= las, las = 0;
        break;
      }
    }
    if (top < 0) return 0;
    stk[++top] = k, h[top] = getkth(rt[stk[top - 1]], rt[k], 1, n, nowc), c[top] = nowc;
    assert(h[top] != -1);
  }
  return 1;
}

void dickdreamer() {
  std::cin >> n;
  for (int i = 1; i <= n; ++i) {
    std::cin >> l[i] >> r[i];
    vec[l[i]].emplace_back(r[i]);
  }
  for (int i = 1; i <= n; ++i) {
    rt[i] = rt[i - 1];
    for (auto x : vec[i]) rt[i] = update(rt[i], 1, n, x, 1);
  }
  std::cin >> q;
  for (int i = 1; i <= q; ++i) {
    int m;
    std::cin >> m;
    std::vector<int> vec(m);
    for (auto &x : vec) std::cin >> x;
    std::cout << check(vec) << '\n';
  }
}

int32_t main() {
#ifdef ORZXKR
  freopen("in.txt", "r", stdin);
  freopen("out.txt", "w", stdout);
#endif
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  int T = 1;
  // std::cin >> T;
  while (T--) dickdreamer();
  // std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
  return 0;
}

qoj7782. Ursa Minor(数据结构,根号分治,分块,哈希)

考虑在差分数组 \(c\) 上考虑这件事情,一次长度为 \(k\) 的修改,等价于让 \(c_l\leftarrow c_l+v,c_{l+k}\leftarrow c_{l+k}-v\)。由于这是在环上操作,所以问题一定等价于每次只能对长度为 \(\gcd(r-l+1,b_s,b_{s+1},\ldots,b_t)\) 的区间操作,假设这个长度是 \(d\)

容易发现合法的条件是所有 \(i\bmod d\) 相同的 \(c_i\) 之和一定是 \(0\),因为每次操作只会让余数相同的两个位置分别加减同样的数。但是直接是难以判断的,因为 \(d\) 可能很大,而每个余数的差分数组之和为 \(0\) 这个限制看起来是难以简单地刻画的。考虑哈希,随机取一个大质数 \(P\) 以及底数 \(B\),我们只判断 \(\sum B^{i\bmod d}c_i\) 是不是 \(0\) 即可。显然这个哈希方式正确的方案一定能判定到,并且错误的方案判定不对的概率大概是 \(\frac{1}{P}\) 的,非常小。

现在只需要考虑怎么求出区间 \(\sum B^{i\bmod d}c_i\) 的值就行了。容易想到根号分治,如果 \(d\leq\sqrt n\),则对每个 \(d\) 单独维护一个数据结构,来维护区间的 \(\sum B^{i\bmod d}c_i\);如果 \(d>\sqrt n\),则把 \(i\bmod d\) 表示成 \(i-d\),由于 \(\left\lfloor\frac{i}{d}\right\rfloor\) 不超过 \(\sqrt n\),所以可以枚举 \(\left\lfloor\frac{i}{d}\right\rfloor\) 的值,同时维护一个表示区间 \(\sum B^ic_i\) 的数据结构即可。两个数据结构一个需要支持 \(O(q\sqrt n)\) 次修改,\(O(q)\) 次查询;一个需要支持 \(O(q)\) 次修改,\(O(q\sqrt n)\) 次查询,分别都可以用分块维护。

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

对于一类形如限制很多个数都等于一个定值,或者限制难以直接刻画的判定性问题,都可以想一想能不能用哈希维护。

代码
#include <bits/stdc++.h>

// #define int int64_t

using i64 = int64_t;
using u64 = uint64_t;

const int kMaxN = 2e5 + 5, kMaxB = 450, kMod = 998244853;

int n, m, q, lim, B = 12897944, iB;
int a[kMaxN], b[kMaxN], diff[kMaxN], st[kMaxN][19];
int pw[kMaxN], ipw[kMaxN];

struct Barrett {
  int64_t m, p;
  void init(int64_t mod) {
    m = ((__int128_t)1 << 64) / mod;
    p = mod;
  }
  Barrett(int64_t mod = 2) { init(mod); }
  inline int64_t operator()(int64_t x) {
    x -= (((__int128_t)x * m) >> 64) * p;
    return x >= p ? x - p : x;
  }
} Reduce(kMod);

int qpow(int bs, int64_t idx = kMod - 2) {
  int ret = 1;
  for (; idx; idx >>= 1, bs = (int64_t)bs * bs % kMod)
    if (idx & 1)
      ret = (int64_t)ret * bs % kMod;
  return ret;
}

inline int fix(i64 x) { return (x % kMod + kMod) % kMod; }
inline int add(int x, int y) { return (x + y >= kMod ? x + y - kMod : x + y); }
inline int sub(int x, int y) { return (x >= y ? x - y : x - y + kMod); }
inline int mul(int x, int y) { return Reduce(1ll * x * y); }
inline void inc(int &x, int y) { (x += y) >= kMod ? x -= kMod : x; }
inline void dec(int &x, int y) { (x -= y) < 0 ? x += kMod : x; }
inline void multi(int &x, int y) { x = Reduce(1ll * x * y); }

int querygcd(int l, int r) {
  int k = std::__lg(r - l + 1);
  return std::__gcd(st[l][k], st[r - (1 << k) + 1][k]);
}

void st_prework() {
  for (int i = 1; i <= m; ++i) st[i][0] = b[i];
  for (int i = 1; i <= std::__lg(m); ++i)
    for (int j = 1; j <= m - (1 << i) + 1; ++j)
      st[j][i] = std::__gcd(st[j][i - 1], st[j + (1 << (i - 1))][i - 1]);
}

void prework() {
  pw[0] = ipw[0] = 1;
  iB = qpow(B);
  for (int i = 1; i <= n; ++i) {
    pw[i] = 1ll * B * pw[i - 1] % kMod;
    ipw[i] = 1ll * iB * ipw[i - 1] % kMod;
  }
  st_prework(), lim = sqrtl(n);
}

namespace SMALL {
struct Block {
  int d, B, s1[kMaxN], s2[kMaxB];
  void prework(int _d) { d = _d, B = std::max<int>(sqrtl(n), 1); }
  void upd(int x, int v) { v = 1ll * v * pw[x % d] % kMod, inc(s1[x], v), inc(s2[(x - 1) / B + 1], v); }
  int query(int l, int r) {
    if (l > r) return 0;
    // assert(1 <= l && l <= r && r <= n);
    int x = (l - 1) / B + 1, y = (r - 1) / B + 1;
    u64 ret = 0;
    if (x == y) {
      for (int i = l; i <= r; ++i) ret += s1[i];
    } else {
      for (int i = l; i <= std::min(x * B, n); ++i) ret += s1[i];
      for (int i = (y - 1) * B + 1; i <= r; ++i) ret += s1[i];
      for (int i = x + 1; i <= y - 1; ++i) ret += s2[i];
    }
    return fix(ret);
  }
} bl[int(kMaxB * 0.7) + 5];

void prework() {
  for (int i = 1; i <= lim; ++i) {
    bl[i].prework(i);
    for (int j = 1; j <= n; ++j) bl[i].upd(j, diff[j]);
  }
}

void upd(int x, int v) {
  int iv = fix(-v);
  for (int i = 1; i <= lim; ++i) {
    bl[i].upd(x, v);
    if (x > 1) bl[i].upd(x - 1, iv);
  }
}

bool check(int l, int r, int d) {
  assert(d <= lim && (r - l + 1) % d == 0);
  // int t = (r - l + 1) / d;
  // for (int i = l; i <= l + d - 2; ++i) {
  //   int pos = getpos(i, d);
  //   if (bl[d].query(pos, pos + t - 1) != 0) return 0;
  // }
  // return 1;
  int sum = bl[d].query(l, r - 1);
  inc(sum, 1ll * sub(a[r], a[l]) * pw[r % d] % kMod);
  return sum == 0;
}
} // namespace SMALL

namespace BIG {
struct Block {
  int B, s1[kMaxN], s2[kMaxB], bel[kMaxN], L[kMaxB], R[kMaxB];
  void prework() {
    B = std::max<int>(sqrtl(n), 1);
    for (int i = 1; i <= (n - 1) / B + 1; ++i) {
      L[i] = (i - 1) * B + 1, R[i] = std::min(i * B, n), s2[i] = s2[i - 1];
      for (int j = L[i]; j <= R[i]; ++j) {
        s1[j] = add(j == L[i] ? 0 : s1[j - 1], 1ll * diff[j] * pw[j] % kMod), inc(s2[i], 1ll * diff[j] * pw[j] % kMod);
        bel[j] = i;
      }
    }
  }
  void upd(int x, int v) {
    v = 1ll * v * pw[x] % kMod;
    for (int i = x; i <= R[bel[x]]; ++i) inc(s1[i], v);
    for (int i = bel[x]; i <= bel[n]; ++i) inc(s2[i], v);
  }
  int query(int l, int r) {
    assert(1 <= l && l <= r && r <= n);
    int x = bel[l], y = bel[r];
    if (x == y) return sub(s1[r], l == L[x] ? 0 : s1[l - 1]);
    else return add(add(sub(s1[R[x]], l == L[x] ? 0 : s1[l - 1]), s1[r]), sub(s2[y - 1], s2[x]));
    // int ret = 0;
    // for (int i = l; i <= r; ++i) {
    //   inc(ret, sub(s1[i], i == L[bel[i]] ? 0 : s1[i - 1]));
    // }
    // return ret;
  }
} bl;

void prework() {
  bl.prework();
}

void upd(int x, int v) {
  bl.upd(x, v);
  if (x > 1) bl.upd(x - 1, fix(-v));
}

bool check(int l, int r, int d, bool fl = 0) {
  assert(d > lim && (r - l + 1) % d == 0);
  int t = (r - l + 1) / d, sum = 0;
  if (fl) {
    std::cerr << "?\n";
    for (int i = l; i <= r; ++i) {
      inc(sum, 1ll * sub(a[i], i == r ? a[l] : a[i + 1]) * pw[i % d] % kMod);
    }
    return sum == 0;
  }
  for (int i = 0; i < t; ++i) {
    int ll = l + d * i, rr = std::min(ll + d - 1, r - 1);
    if (ll <= rr) inc(sum, 1ll * bl.query(ll, rr) * ipw[ll] % kMod);
    // for (int j = ll; j <= rr; ++j) inc(sum, 1ll * sub(a[j], a[j + 1]) * pw[j] % kMod * ipw[ll] % kMod);
  }
  // for (int i = l; i < r; ++i) inc(sum, 1ll * sub(a[i], a[i + 1]) * pw[(i - l) % d] % kMod);
  inc(sum, 1ll * sub(a[r], a[l]) * pw[d - 1] % kMod);
  // sum = fix(sum);
  return sum == 0;
}
} // namespace BIG

void dickdreamer() {
  std::cin >> n >> m >> q;
  for (int i = 1; i <= n; ++i) std::cin >> a[i], a[i] %= kMod;
  for (int i = 1; i <= m; ++i) std::cin >> b[i];
  for (int i = 1; i <= n; ++i) diff[i] = fix(a[i] - a[i + 1]);
  bool fl = (a[1] == 521405462);
  prework();
  lim *= 0.7;
  SMALL::prework(), BIG::prework();
  for (int i = 1; i <= q; ++i) {
    std::string op;
    int l, r, s, t;
    std::cin >> op;
    if (op[0] == 'U') {
      std::cin >> l >> r;
      SMALL::upd(l, fix(r - a[l])), BIG::upd(l, fix(r - a[l]));
      a[l] = fix(r);
      // if (n == 2e5 && m == 2e5 && q == 2e5) std::cerr << op << ' ' << l << ' ' << r << '\n';
    } else {
      static int cs = 0;
      std::cin >> l >> r >> s >> t;
      ++cs;
      int d = std::__gcd(r - l + 1, querygcd(s, t));
      if (d <= lim) std::cout << (SMALL::check(l, r, d) ? "Yes\n" : "No\n");
      else std::cout << (BIG::check(l, r, d, cs == 19) ? "Yes\n" : "No\n");
      if (fl && cs == 19) std::cerr << op << ' ' << l << ' ' << r << ' ' << s << ' ' << t << ' ' << d << ' ' << lim << '\n';
    }
  }
}

int32_t main() {
#ifdef ORZXKR
  freopen("in.txt", "r", stdin);
  freopen("out.txt", "w", stdout);
#endif
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  int T = 1;
  // std::cin >> T;
  while (T--) dickdreamer();
  // std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
  return 0;
}

pjudge21808. 【PR #12】并查集(贪心,adhoc,找性质,拆贡献)

首先如果直接算答案,是难以刻画 find 函数调用次数这个东西的,所以需要找到一个相对好看的调用次数刻画方式。

考虑拆贡献,对于并查集上的每条边计算这条边贡献的次数。注意这么算每次连边会少算两次,因为 find 调用次数是经过的点数而不是边数,最后还要加上 \(2(n-1)\) 才是答案。设 \(S_i\) 表示 \(i\) 在并查集子树里的点集。如果现在连了一条 \(A\to B\) 的边,那么这条边贡献的次数就是 \(S_{A}\) 里的点和 \(S_{A}\) 外的点构成的边数减一,也就是 \(\sum_{x\in S_A}deg_x-2(|S_A|-1)-1=\sum_{x\in S_{A}}(deg_x-2)+1\)。如果将 \(\sum_{x\in S_i}(deg_x-2)\) 看成一个集合的权值,那么合并两个集合时一定是权值小的集合是父亲,大的集合是儿子。显然我们只需要最大化并查集树上每个点子树构成集合的权值的和。

然后经过手玩或者观察可以发现:对于两个集合 \(S_1,S_2\),如果连了一条 \(S_1\to S_2\) 的边,且 \(|S_2|>1\) 是不优的,调整成 \(|S_2|\) 里的点一个一个挂在 \(S_1\) 的父亲上一定不劣,感性理解就是连边连成链深度会更高,看起来会更优(我不会证)。根据这个性质,最终连出来的并查集树一定构成一条链,即连边的过程是初始将集合 \(S\) 设为 \(\{x\}\)\(x\) 任选。每次选择一个与 \(S\) 内的点有连边的点 \(y\),在并查集树上连 \(S\to \{y\}\) 的边,并把 \(y\) 加入 \(S\)

先枚举 \(x\),钦定 \(x\) 为树根,那么问题变为需要给每个点 \(i\) 分配一个 \(p_i\) 的权重,\(p_i\) 构成 \(\{0,1,\ldots,n-1\}\) 的排列且每个点的权重都小于它们父亲的权重,要求最大化 \(\sum_{i=1}^{n}{p_i(deg_i-2)}\)。这是个经典的贪心问题,设 \(deg_i-2\)\(i\) 的权值,每次选择树上权值最大的点,那么这个点一定会在其父亲选了之后紧接着选,将其与父亲合并,新权值是平均数,这么一直合并下去直到只剩一个点时结束,实现可以用并查集和 set 维护。时间复杂度是 \(O(n^2\log n)\)

对于这类看起来很难刻画的贪心问题,可以选择拆贡献来让答案变得容易刻画。

代码
#include <bits/stdc++.h>

// #define int int64_t

const int kMaxN = 2e3 + 5;

int n, now;
int deg[kMaxN], p[kMaxN];
std::vector<int> G[kMaxN];

struct Frac {
  int x, y;
  Frac(int _x = 0, int _y = 1) : x(_x), y(_y) {}
  friend bool operator <(Frac a, Frac b) { return a.x * b.y < a.y * b.x; }
};

std::set<std::pair<Frac, int>> st;

struct DSU {
  int fa[kMaxN], sz[kMaxN], sum[kMaxN];
  void init(int n) {
    st.clear();
    for (int i = 1; i <= n; ++i) fa[i] = i, sz[i] = 1, sum[i] = deg[i] - 2, st.emplace(Frac(sum[i], 1), i);
  }
  int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); }
  void unionn(int x, int y) {
    int fx = find(x), fy = find(y);
    if (fx != fy) {
      st.erase({Frac(sum[fx], sz[fx]), fx});
      st.erase({Frac(sum[fy], sz[fy]), fy});
      fa[fx] = fy, now += sum[fy] * sz[fx], sz[fy] += sz[fx], sum[fy] += sum[fx];
      st.emplace(Frac(sum[fy], sz[fy]), fy);
    }
  }
} dsu;

void dfs1(int u, int fa) {
  p[u] = fa;
  for (auto v : G[u]) {
    if (v == fa) continue;
    dfs1(v, u);
  }
}

void dickdreamer() {
  std::cin >> n;
  for (int i = 1; i < n; ++i) {
    int u, v;
    std::cin >> u >> v; ++u, ++v;
    G[u].emplace_back(v), G[v].emplace_back(u);
    ++deg[u], ++deg[v];
  }
  int ans = 0;
  for (int i = 1; i <= n; ++i) {
    dfs1(i, 0);
    dsu.init(n);
    now = 0;
    for (int c = 1; c <= n - 1; ++c) {
      assert(st.size() == n - c + 1);
      auto it = --st.end();
      if (!p[it->second]) --it; 
      int t = it->second;
      dsu.unionn(t, p[t]);
    }
    ans = std::max(ans, now + 3 * (n - 1));
  }
  std::cout << ans << '\n';
}

int32_t main() {
#ifdef ORZXKR
  freopen("in.txt", "r", stdin);
  freopen("out.txt", "w", stdout);
#endif
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  int T = 1;
  // std::cin >> T;
  while (T--) dickdreamer();
  // std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
  return 0;
}
posted @ 2026-01-26 20:48  下蛋爷  阅读(4)  评论(0)    收藏  举报