Solution Set #2

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

CF739E. Gosha is hunting(凸性,wqs二分)

首先容易猜到如果固定 \(b\),最终的答案关于 \(a\) 是上凸的,所以我们可以二分斜率 \(k\),然后会发现有下面四种贡献:

  1. 选 A、B:\(p_i+u_i-p_iu_i-k\)
  2. 只选 A:\(p_i-k\)
  3. 只选 B:\(u_i\)
  4. 不选:\(0\)

由于这时已经没有 A 的限制了,所以我们只要求选 B 的不超过 \(b\) 个,选择 \(\max(p_i+u_i-p_iu_i-k,u_i)-\max(p_i-k,0)\) 最大的 \(b\) 个,还需要再让权值总和尽量大的时候最大化选 A 的数量。

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

对于这类需要固定某种东西的数量的最优化题可以想一想能不能 wqs 二分。

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

// #define int int64_t

using f64 = long double;

const int kMaxN = 2e3 + 5;
const f64 kEps = 1e-6;

int n, A, B;
f64 p[kMaxN], u[kMaxN];

std::pair<f64, int> calc(f64 k) {
  static f64 val[kMaxN];
  static int id[kMaxN], det[kMaxN];
  for (int i = 1; i <= n; ++i) {
    id[i] = i, val[i] = std::max(u[i], p[i] + u[i] - p[i] * u[i] - k) - std::max<f64>(p[i] - k, 0);
    det[i] = 0;
    if (p[i] + u[i] - p[i] * u[i] - k >= u[i] - kEps) ++det[i];
    if (p[i] - k >= 0 - kEps) --det[i];
  }
  std::sort(id + 1, id + 1 + n, [&] (int i, int j) {
    if (fabs(val[i] - val[j]) < kEps) return det[i] > det[j];
    else return val[i] > val[j];
  });
  f64 sum = 0;
  int cnt = 0;
  for (int t = 1; t <= n; ++t) {
    int i = id[t];
    if (t <= B) {
      if (p[i] + u[i] - p[i] * u[i] - k >= u[i] - kEps) sum += p[i] + u[i] - p[i] * u[i] - k, ++cnt;
      else sum += u[i];
    } else {
      if (p[i] - k >= 0 - kEps) sum += p[i] - k, ++cnt;
      else sum += 0;
    }
  }
  return {sum, cnt};
}

void dickdreamer() {
  std::cin >> n >> A >> B;
  for (int i = 1; i <= n; ++i) std::cin >> p[i];
  for (int i = 1; i <= n; ++i) std::cin >> u[i];
  f64 L = 0, R = 2e4, res = R;
  while (L + kEps < R) {
    f64 mid = (L + R) / 2;
    if (calc(mid).second >= A) L = res = mid;
    else R = mid;
  }
  std::cout << std::fixed << std::setprecision(10) << calc(res).first + res * A << '\n';
  // std::cerr << calc(0).first << ' ' << calc(0).second << '\n';
  // for (int i = 0; i <= 1e5; ++i) std::cerr << calc(i * kEps).second << '\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;
}

qoj11713. Subsequence Queries(计数,矩阵乘法,矩阵的逆,查询区间矩阵乘积可以转矩阵的逆)

先假设字符集大小只有 \(3\),容易得到转移矩阵 \(A_0=\begin{pmatrix}1&0&0&0\\1&1&0&0\\1&0&1&0\\1&0&0&1\end{pmatrix}\)\(A_1=\begin{pmatrix}1&1&0&0\\0&1&0&0\\0&1&1&0\\0&1&0&1\end{pmatrix}\)\(A_2=\begin{pmatrix}1&0&1&0\\0&1&1&0\\0&0&1&0\\0&0&1&1\end{pmatrix}\),最终 \([l,r]\)

的答案即为 \(\begin{pmatrix}0&0&0&1\end{pmatrix}\times A_{s_l}\times A_{s_{l+1}}\times\ldots\times A_{s_r}\times\begin{pmatrix}1\\1\\1\\1\end{pmatrix}\),直接区间查询矩阵乘积最优只能做到 \(O(q\log n\Sigma^3)\),且难以优化。

注意到矩阵 \(A_i\) 是有逆的,所以可以将区间矩阵乘积转换为 \(r\) 的前缀积乘 \(l-1\) 的前缀积的逆,先对式子求个转置,答案变为 \(\begin{pmatrix}1\\1\\1\\1\end{pmatrix}^{T}\times A_{s_r}^{T}\times A_{s_{r-1}}^{T}\times\ldots\times A_{s_1}^{T}\times (A_{s_1}^{T})^{-1}\times\ldots (A_{s_{l-1}}^{T})^{-1}\times\begin{pmatrix}0\\0\\0\\1\end{pmatrix}\)

\(P_{i}=\begin{pmatrix}1\\1\\1\\1\end{pmatrix}^{T}\times A_{s_r}^{T}\times A_{s_{r-1}}^{T}\times\ldots\times A_{s_i}^{T}\)\(Q_i=(A_{s_1}^{T})^{-1}\times\ldots (A_{s_{i}}^{T})^{-1}\times\begin{pmatrix}0\\0\\0\\1\end{pmatrix}\),我们就只需要预处理出出 \(P_r\)\(Q_{l-1}\),最后查询时可以 \(O(\Sigma)\) 相乘。

先考虑怎么求 \(P_i\)。不妨设 \(s_i=0\)\(A_{s_i}^{T}=\begin{pmatrix}1&1&1&1\\0&1&0&0\\0&0&1&0\\0&0&0&1\end{pmatrix}\),如果把 \(P_{i-1}\) 写成 \(\begin{pmatrix}v_{i-1,0}\\v_{i-1,1}\\v_{i-1,2}\\v_{i-1,3}\end{pmatrix}\),则 \(P_i=\begin{pmatrix}1&1&1&1\\0&1&0&0\\0&0&1&0\\0&0&0&1\end{pmatrix}\times\begin{pmatrix}v_{i-1,0}\\v_{i-1,1}\\v_{i-1,2}\\v_{i-1,3}\end{pmatrix}=\begin{pmatrix}v_{i-1,0}+v_{i-1,1}+v_{i-1,2}+v_{i-1,3}\\v_{i-1,1}\\v_{i-1,2}\\v_{i-1,3}\end{pmatrix}\)

容易发现每次只有 \(s_i\) 这一行会变化,所以维护一下列的和即可。

然后是求 \(Q_i\)。同样设 \(s_i=0,(A_{s_i}^{T})^{-1}=\begin{pmatrix}1&-1&-1&-1\\0&1&0&0\\0&0&1&0\\0&0&0&1\end{pmatrix}\),把 \(Q_{i-1}\) 写成 \(\begin{pmatrix}v_{i-1,0}\\v_{i-1,1}\\v_{i-1,2}\\v_{i-1,3}\end{pmatrix}^{T}\),则 \(Q_i=\begin{pmatrix}v_{i-1,0}\\v_{i-1,1}\\v_{i-1,2}\\v_{i-1,3}\end{pmatrix}^{T}\times\begin{pmatrix}1&-1&-1&-1\\0&1&0&0\\0&0&1&0\\0&0&0&1\end{pmatrix}=\begin{pmatrix}v_{i-1,0}\\v_{i-1,1}-v_{i-1,0}\\v_{i-1,2}-v_{i-1,0}\\v_{i-1,3}-v_{i-1,0}\end{pmatrix}^{T}\)

这个状态下只有 \(s_i\) 这一列不会变,其余的列都会减去 \(v_{i-1,0}\),这启发我们维护一个全局减的标记 \(D_i\),即将 \(Q_{i-1}\) 写成 \(\begin{pmatrix}v_{i-1,0}-D_{i-1}\\v_{i-1,1}-D_{i-1}\\v_{i-1,2}-D_{i-1}\\v_{i-1,3}-D_{i-1}\end{pmatrix}^{T}\),那么 \(Q_i=\begin{pmatrix}v_{i-1,0}-D_{i-1}\\v_{i-1,1}-v_{i-1,0}\\v_{i-1,2}-v_{i-1,0}\\v_{i-1,3}-v_{i-1,0}\end{pmatrix}^{T}\),让 \(D_i=v_{i-1,0}\)\(v_{i,0}=2v_{i-1,0}-D_{i-1}\)\(v_{i,1}=v_{i-1,1}\)\(v_{i,2}=v_{i-1,2}\) 即可,同样每次的变化量只有 \(O(\Sigma)\)

总的复杂度就是 \(O((n+q)\Sigma)\)

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

// #define int int64_t

const int kMaxN = 1e6 + 5, kMod = 1e9 + 7;

int n, q, _p, _q, _r;
int a[kMaxN], b[kMaxN], x[kMaxN], y[kMaxN], z[kMaxN];
int f[kMaxN][53], g[kMaxN][53];
std::string str;

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; }

int getid(char c) {
  if (c >= 'A' && c <= 'Z') return c - 'A';
  else return c - 'a' + 26;
}

void getf() {
  static int mat[53][53], s[53];
  memset(mat, 0, sizeof(mat));
  memset(s, 0, sizeof(s));
  for (int i = 0; i <= 52; ++i) mat[i][i] = s[i] = 1;
  for (int i = 0; i <= n; ++i) {
    // if (i == n) {
    //   for (int i = 0; i <= 52; ++i) {
    //     for (int j = 0; j <= 52; ++j)
    //       std::cout << mat[i][j] << " \n"[j == 52];
    //   }
    // }
    for (int j = 0; j <= 52; ++j) f[i][j] = s[j];
    if (i < n) {
      int c = getid(str[i + 1]);
      for (int j = 0; j <= 52; ++j) {
        int tmp = mat[c][j];
        mat[c][j] = s[j], s[j] = (2ll * s[j] - tmp + kMod) % kMod;
      }
    }
  }
}

void getg() {
  static int mat[53][53], d[53];
  memset(mat, 0, sizeof(mat));
  memset(d, 0, sizeof(d));
  for (int i = 0; i <= 52; ++i) mat[i][i] = 1;
  for (int i = 0; i <= n; ++i) {
    for (int j = 0; j <= 52; ++j) g[i][j] = sub(mat[j][52], d[j]);
    if (i < n) {
      int c = getid(str[i + 1]);
      for (int j = 0; j <= 52; ++j) {
        int tmp = mat[j][c];
        mat[j][c] = (2ll * mat[j][c] - d[j] + kMod) % kMod, d[j] = tmp;
      }
    }
  }
}

void prework() {
  getf(), getg();
}

int query(int l, int r) {
  int ret = 0;
  for (int i = 0; i <= 52; ++i) ret = (ret + 1ll * f[r][i] * g[l - 1][i]) % kMod;
  // for (int i = 0; i <= 52; ++i) std::cerr << f[r][i] << " \n"[i == 52];
  // for (int i = 0; i <= 52; ++i) std::cerr << g[r][i] << " \n"[i == 52];
  return ret;
}

void dickdreamer() {
  std::cin >> str >> q >> a[0] >> b[0] >> _p >> _q >> _r;
  n = str.size(), str = " " + str;
  prework();
  for (int i = 1; i <= q; ++i) {
    a[i] = (1ll * _p * a[i - 1] + 1ll * _q * b[i - 1] + z[i - 1] + _r) % kMod;
    b[i] = (1ll * _p * b[i - 1] + 1ll * _q * a[i - 1] + z[i - 1] + _r) % kMod;
    x[i] = std::min(a[i] % n, b[i] % n) + 1;
    y[i] = std::max(a[i] % n, b[i] % n) + 1;
    z[i] = query(x[i], y[i]);
    // std::cerr << x[i] << ' ' << y[i] << ' ' << z[i] << '\n';
  }
  std::cout << z[q] << '\n';
  // std::cerr << query(3, 5) << '\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;
}

P8522. [IOI 2021] 地牢游戏(倍增,倍增分块)

显然要倍增,但是直接是不好维护的,因为 \(z\geq s_i\)\(z<s_i\) 时的两种情况很难放到一起。注意到这里当 \(z\geq s_i\) 时会让 \(z\) 加上 \(s_i\),考虑倍增分块。

假设 \(z\)\([B^k,B^{k+1})\) 里面,倍增这个块内的走法。如果 \(s_i<B^k\),则一定会走 \((w_i,s_i)\);如果 \(s_i\geq B^{k+1}\),一定会走 \((l_i,p_i)\)。当 \(B^k\leq s_i<B^{k+1}\)\(i\) 会有两种可能的走法。由于 \(B^k\leq s_i\),所以这样的 \((w_i,s_i)\) 在这个块内至多走 \(B\) 次,我们就可以先钦定所有这样的 \(i\) 都要走 \((l_i,p_i)\),且在走之前 \(z\) 不能超过 \(s_i-1\),称这为一个限制。

倍增的时候把限制也压进去,查询的时候找到第一次走出块或者超过限制的位置暴力判断,这样的位置只有 \(B\cdot\log_BV\) 个,直接倍增复杂度就是对的。

具体地,设 \(f_{k,i,j}\) 表示在第 \(k\) 个块的限制下,走至多 \(2^j\) 步走到的点(如果到 \(n\) 就停止),\(g_{k,i,j}\) 表示走的距离,\(h_{k,i,j}\) 表示要满足 \(2^j\) 个限制的最大初始值。容易 \(O(n\log_BV\log_2V)\) 预处理。

总复杂度就是 \(O(n\log_BV\log_2V+qB\log_BV\log_2V)\),取 \(B=64\) 可通过。

对于这类形如判断 \(\geq x\) 时给变量加 \(x\) 的题,可以考虑倍增分块做。

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

// #define int int64_t

using i64 = long long;
using tiii = std::tuple<int, i64, i64>;

const int kMaxN = 4e5 + 5, kMaxV = 1e7 + 5, kB = 64, kD = 7, k2D = 26;

int n, d;
int s[kMaxN], p[kMaxN], w[kMaxN], l[kMaxN];
i64 L[kD], R[kD];
tiii nxt[kD][kMaxN][k2D];

void prework() {
  d = 1, L[1] = 0, R[1] = 1;
  for (;;) {
    if (L[d] > kMaxV) R[d] = 1e18;
    for (int i = 0; i <= n; ++i) {
      if (i != n && s[i] >= R[d]) nxt[d][i][0] = {l[i], p[i], R[d] - 1};
      else if (i != n && s[i] < L[d]) nxt[d][i][0] = {w[i], s[i], R[d] - 1};
      else if (i != n) nxt[d][i][0] = {l[i], p[i], s[i] - 1};
      else nxt[d][i][0] = {i, 0, s[i] - 1};
    }
    for (int i = 1; i <= 25; ++i) {
      for (int j = 0; j <= n; ++j) {
        auto [pos, dis, lim] = nxt[d][j][i - 1];
        // std::cerr << pos << ' ' << dis << '\n';
        auto [pos1, dis1, lim1] = nxt[d][pos][i - 1];
        nxt[d][j][i] = {pos1, dis + dis1, std::min(lim, lim1 - dis)};
      }
    }
    if (L[d] > kMaxV) break;
    ++d, L[d] = R[d - 1], R[d] = L[d] * kB;
    // std::cerr << d << ' ' << L[d] << ' ' << R[d] << '\n';
  }
}

int getbel(i64 x) {
  for (int i = 1; i <= d; ++i)
    if (L[i] <= x && x < R[i])
      return i;
  assert(0);
}

void init(int n, std::vector<int> s, std::vector<int> p, std::vector<int> w, std::vector<int> l) {
  ::n = n;
  for (int i = 0; i < n; ++i) ::s[i] = s[i], ::p[i] = p[i], ::w[i] = w[i], ::l[i] = l[i];
  prework();
  return;
}

long long simulate(int x, int _z) {
  int b = 1;
  i64 z = _z;
  for (; x != n;) {
    for (; R[b] <= z; ++b) {}
    assert(L[b] <= z && z < R[b]);
    for (int i = 25; ~i; --i) {
      auto [pos, dis, lim] = nxt[b][x][i];
      if (z + dis < R[b] && z <= lim) {
        x = pos, z += dis;
      }
    }
    if (x == n) break;
    if (z >= s[x]) z += s[x], x = w[x];
    else z += p[x], x = l[x];
  }
  return z;
}

qoj8820. Exchanging Kubic 2(计数dp,最优化,贪心,弱化条件)

考虑给定数组 \(a\) 怎么求答案。

\(b_i=a_i-a_{i-1}\),钦定 \(b_1=0\)。那么操作 \(x\) 等价于找到 \(1\leq i\leq x\)\(b_i>0\) 的最大 \(i\)\(x<j\leq n\)\(b_j>0\) 的最小 \(j\),让 \(b_i\leftarrow b_i-1,b_j\leftarrow b_j-1\),找不到的话这一边就不减。这个东西是很难优化的,因为 \(i\)\(j\) 都必须是这一边离 \(x\) 最近的这个条件根本无法刻画。

考虑弱化条件,我们不妨把 \(i\) 最大和 \(j\) 最小这两个条件去掉,随便取满足条件的任意 \(i,j\),由于 \(b_i\) 的值一定是要尽量靠近中心,所以不选最近的一定不优。现在我们就只要求 \(i\leq x\)\(j>x\) 了。问题变为给每个点 \(x\) 找到 \(L_x\)\(R_x\),使得 \(L_x\leq x\) 或者 \(L_x=-1\)\(R_x>x\) 或者 \(R_x=-1\),并且 \(L_j=i\)\(j\) 的数量加 \(R_k=i\)\(k\) 的数量之和不超过 \(b_i\),要最大化匹配了的数量之和,最终的答案就是所有 \(\sum b_i\) 条边中没有被匹配的边数。

如果只有 \(R\) 求匹配数是好做的,正着扫一遍,维护一下还没匹配的点的数量即可。但是加上 \(L\) 的限制就需要正着贪心扫两遍,一次前面是边后面是点,一次前面是点后面是边,每次都选择最前面的进行匹配,但难以压进状态。

考虑进行下面的过程:从前往后扫,记录变量 \(s\) 表示目前的边数(实际意义不完全是边的数量),每次扫到 \(i\) 就先让 \(s\leftarrow s+b_i\)。如果 \(s<i\),说明最多只能给 \([1,i]\) 这些点的 \(L\) 供应 \(s\) 条边,剩下 \(i-s\) 个点是无论如何都匹配不上的,我们假定 \(b_1\) 凭空出现了 \(i-s\) 条边,让 \(s\leftarrow i\)。如果 \(s>n+i-1\),由于 \([1,i]\) 这些 \(b_i\) 只能供应 \([1,n]\)\(L_x\)\([1,i-1]\)\(R_x\),最多只能用上 \(n+i-1\) 条边,剩下的 \(s-(n+i-1)\) 条边一定会浪费,让 \(ans\leftarrow ans+s-(n+i-1),s\leftarrow n+i-1\)。这么扫下去,最终的 \(ans\) 就是答案。

证明就考虑改变第一次贪心的过程,从前往后扫,维护一个队列表示没匹配的边。扫到点 \(i\) 时,如果队列里有边,则直接取队列里编号最小的边匹配。如果没有边就在 \(b_1\) 处凭空变出一条边,让 \(i\) 去匹配这条边。这么扫完,按照新的 \(b\) 去再跑一遍同样的过程,匹配到旧边的点数和变出边之前匹配能匹配到边的点数是一样的,且匹配后每种边剩余的数量不变,还能得到被匹配到的所有边一定构成一个大小为 \(n\) 的前缀。

根据上面的结论,\([1,i]\) 的前缀第一次扫完被 \(L\) 匹配剩下的边数就是 \(\max(s-n,0)\),这个时候就能把第二次操作需要用到的东西压到状态里了!如果 \(s-n>i-1\),就说明有 \(s-(n+i-1)\) 条边会浪费,加进答案,并让 \(b_i\) 减去这么多(显然不会减到负数),由于只要 \(s\) 第一次大于 \(n+i-1\) 时就不会再出现 \(s<i\) 的情况,所以这里的操作和第一次扫的操作不会有任何影响!如果所有 \(\max(s-n,0)\leq i-1\),此时所有的边肯定都能找到匹配,也就不会有更多的贡献了。

总结一下:每次让 \(s\leftarrow s+b_i,s\leftarrow\max(s,i),ans\leftarrow ans+\max(s-(n+i-1),0),s\leftarrow\min(s,n+i-1)\)

直接对这个东西 dp 即可,时间复杂度:\(O(n^3)\)

对于这类操作很复杂、难以刻画的问题可以考虑弱化条件后再做。

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

// #define int int64_t

const int kMaxN = 405, kMod = 998244353;

int n;
std::vector<int> vec[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; }

struct Info {
  int cnt, sum;
  Info(int _cnt = 0, int _sum = 0) : cnt(_cnt), sum(_sum) {}
  friend Info operator +(Info a, Info b) { return {add(a.cnt, b.cnt), add(a.sum, b.sum)}; }
  friend Info operator *(Info a, Info b) { return {1ll * a.cnt * b.cnt % kMod, (1ll * a.cnt * b.sum + 1ll * a.sum * b.cnt) % kMod}; }
} f[2][kMaxN * 2][kMaxN * 2];

void dickdreamer() {
  std::cin >> n;
  for (int i = 1; i <= n; ++i) {
    int sz;
    std::cin >> sz;
    vec[i].resize(sz);
    for (auto &x : vec[i]) std::cin >> x;
    std::sort(vec[i].begin(), vec[i].end());
  }
  int o = 0;
  for (int i = 0; i < vec[1].size(); ++i) f[o][i][1] = {1, 0};
  for (int i = 2; i <= n; ++i) {
    static Info tmp[kMaxN * 5];
    o ^= 1;
    memset(f[o], 0, sizeof(f[o]));
    memset(tmp, 0, sizeof(tmp));
    // for (int j = 0; j < vec[i].size(); ++j) {
    //   for (int k = 0; k < vec[i - 1].size(); ++k) {
    //     if (vec[i][j] < vec[i - 1][k]) continue;
    //     for (int s = i - 1; s <= n + i - 2; ++s) {
    //       int now = s + vec[i][j] - vec[i - 1][k];
    //       now = std::max(now, i);
    //       int det = 0;
    //       if (now >= n + i - 1) det = now - (n + i - 1), now = n + i - 1;
    //       f[o][j][now] = f[o][j][now] + f[o ^ 1][k][s] * Info{1, det};
    //     }
    //   }
    // }
    int base = 0, mx = 0;
    for (auto x : vec[i - 1]) base = std::max(base, x);
    for (int j = 0, k = 0; j < vec[i].size(); ++j) {
      for (; k < vec[i - 1].size() && vec[i][j] >= vec[i - 1][k]; ++k) {
        for (int s = i - 1; s <= n + i - 2; ++s) {
          tmp[s - vec[i - 1][k] + base] = tmp[s - vec[i - 1][k] + base] + f[o ^ 1][k][s];
          mx = std::max(mx, s - vec[i - 1][k] + base);
        }
      }
      for (int s = 0; s <= mx; ++s) {
        int now = s + vec[i][j] - base;
        now = std::max(now, i);
        int det = 0;
        if (now >= n + i - 1) det = now - (n + i - 1), now = n + i - 1;
        f[o][j][now] = f[o][j][now] + tmp[s] * Info{1, det};
      }
    }
  }
  int ans = 0;
  for (int i = 0; i < vec[n].size(); ++i)
    for (int j = n; j <= 2 * n - 1; ++j)
      inc(ans, f[o][i][j].sum);
  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;
}

qoj12334. Tritwise Mex(卷积,分治,Karatsuba)

注意到每位取 \(\text{mex}\) 是非常难以刻画的,很难用类似 fwt 的东西维护,我们转换思路,用卷积的另一种求法分治来做。

假设我们要求 \(a\times b=c\),设 \(a=\{a_0,a_1,a_2\},b=\{b_0,b_1,b_2\}\),那么我们要求的 \(c=\{a_1b_1+a_1b_2+a_2b_1+a_2b_2,a_0b_0+a_0b_2+a_2b_0,a_0b_1+a_1b_0\}\)。此时需要做 9 次乘法,考虑用加减法来减少乘法次数。

可以发现 \(c_0=(a_1+a_2)(b_1+b_2),c_1=(a_0+a_1+a_2)(b_0+b_1+b_2)-c_0-c_2\),因此只需要做 4 次乘法。总复杂度是 \(T(n)=4T(n-1)+3^n=4^n\)

对于这类难以快速维护的类 fwt 卷积问题可以考虑按位分治来做。

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

#define int int64_t

int n;
int pw[13];

std::vector<int> add(std::vector<int> a, std::vector<int> b) {
  assert(a.size() == b.size());
  std::vector<int> c(a.size());
  for (int i = 0; i < c.size(); ++i) c[i] = a[i] + b[i];
  return c;
}

std::vector<int> sub(std::vector<int> a, std::vector<int> b) {
  assert(a.size() == b.size());
  std::vector<int> c(a.size());
  for (int i = 0; i < c.size(); ++i) c[i] = a[i] - b[i];
  return c;
}

std::vector<int> mul(std::vector<int> a, std::vector<int> b) {
  assert(a.size() == b.size());
  if (a.size() == 3) {
    return {a[1] * b[1] + a[1] * b[2] + a[2] * b[1] + a[2] * b[2], a[0] * b[0] + a[0] * b[2] + a[2] * b[0], a[0] * b[1] + a[1] * b[0]};
  }
  std::vector<int> A[3], B[3];
  for (int o = 0; o < 3; ++o) {
    for (int i = 0; i < a.size() / 3; ++i) A[o].emplace_back(a[a.size() / 3 * o + i]);
    for (int i = 0; i < b.size() / 3; ++i) B[o].emplace_back(b[b.size() / 3 * o + i]);
  }
  std::vector<int> C[3];
  C[0] = mul(add(A[1], A[2]), add(B[1], B[2]));
  C[2] = add(mul(A[0], B[1]), mul(A[1], B[0]));
  C[1] = sub(sub(mul(add(add(A[0], A[1]), A[2]), add(add(B[0], B[1]), B[2])), C[0]), C[2]);
  std::vector<int> c;
  for (auto x : C[0]) c.emplace_back(x);
  for (auto x : C[1]) c.emplace_back(x);
  for (auto x : C[2]) c.emplace_back(x);
  return c;
}

void dickdreamer() {
  std::cin >> n;
  pw[0] = 1;
  for (int i = 1; i <= n; ++i) pw[i] = 3 * pw[i - 1];
  std::vector<int> a(pw[n]), b(pw[n]);
  for (auto &x : a) std::cin >> x;
  for (auto &x : b) std::cin >> x;
  auto c = mul(a, b);
  for (auto x : c) std::cout << x << ' ';
}

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;
}

CF1336E2. Chiori and Doll Picking (hard version)(线性基,fwt,根号分治)

首先题目让我们求异或和的方案数,先求出一组 \(a\) 数组的线性基,设其为 \(A\),大小为 \(cnt\)。那么只需要对 \(A\) 这个基里面的元素求答案,最后每个答案再乘上 \(2^{n-cnt}\) 即可。

容易得到 \(O(2^{cnt})\) 的暴搜做法,由于这个东西看起来就很难直接做优于 \(O(2^{cnt})\),所以考虑找一个 \(O(2^{m-cnt})\) 的做法来根号分治。

先把答案写成 fwt 的形式,即设 \(F=\prod_{i}{(1+x^i)}\),那么我们只需要对于所有 \(c\),求出 \(\sum_i[pc(i)=c]F_i\)。但是还是很难得到 \(F\) 数组,这里转而求 \(G=FWT(F)\)

由于 \(FWT(1+x^k)=\sum_{i}{[1+(-1)^{pc(i\& k)}]x^i}\),所以最终的 \(G_i\) 只有 \(0\)\(2^{cnt}\) 两种取值,且所有满足 \(G_i=2^{cnt}\)\(i\) 都和所有 \(A\) 的元素 \(v\)\(pc(i\&v)\equiv0\pmod 2\),设这样的 \(i\) 构成的集合是 \(S\)。又因为 \(pc(i\&v)+pc(j\&v)\equiv pc((i\oplus j)\&v)\pmod 2\),所以如果 \(i\in S,j\in S\)\(i\oplus j\) 也在 \(S\) 中,也就是说 \(S\) 中的所有元素构成了一个线性空间。那么如果我们得到了 \(S\) 的任意一组基,就能得到整个 \(S\) 的信息了,设 \(S\) 的基是 \(B\)

先通过高斯消元把 \(A\) 消成每个主元列都只有恰好一个 \(1\) 的形式,例如 \(\begin{pmatrix}1&1&0&1&0\\0&0&0&0&0\\0&0&1&1&0\\0&0&0&0&0\\0&0&0&0&1\end{pmatrix}\),现在只需要对这个基求解。因为如果我们确定了 \(S\) 中元素 \(m-cnt\) 个非主元列的值,剩下 \(cnt\) 个主元列的值根据目前的 \(1\) 和每一行交的 popcount 的奇偶性即可确定,所以 \(|S|=2^{m-cnt},|B|=m-cnt\)。构造 \(B\) 就考虑对于 \(A\) 的每个非主元 \(x\),让 \(B_{x,x}=1\)。对于所有主元 \(y\)\(B_{x,y}=A_{y,x}\),其余位置都是 \(0\)。容易证明这样的 \(B_x\)\(A\) 的每一行的交一定是偶数。

由于我们只需要求最终每种 popcount 的答案,所以我们也只关心 \(S\) 中每种 popcount 出现的次数,先暴搜 \(B\) 中每个元素选或不选来得到 \(cnt_i\) 表示 \(S\) 中 popcount 为 \(i\) 的数的数量。fwt 回去的时候枚举一下新数在这 \(i\) 个为 \(1\) 的位置里 \(1\) 的数量 \(j\)\(m-i\) 个为 \(0\) 的位置里为 \(1\) 的数量 \(k\),转移为 \(res_{j+k}\leftarrow cnt_i\times\binom{i}{j}\times\binom{m-i}{k}\)。这部分的时间复杂度是 \(O(2^{m-cnt}+m^3)\)

将两部分综合起来,最终的时间复杂度是 \(O(2^{m/2}+m^3+nm)\)

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

// #define int int64_t

using i64 = int64_t;
using u64 = uint64_t;

const int kMaxN = 2e5 + 5, kMaxM = 65, kMod = 998244353;

int n, m, cnt;
i64 a[kMaxN], p[kMaxM], q[kMaxM];
int C[kMaxM][kMaxM], coef[kMaxM][kMaxM], ct[kMaxM], res[kMaxM];

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; }
inline int getop(int x) { return (~x & 1) ? 1 : (kMod - 1); }

void prework(int n = 53) {
  C[0][0] = 1;
  for (int i = 1; i <= n; ++i) {
    C[i][0] = 1;
    for (int j = 1; j <= i; ++j) C[i][j] = add(C[i - 1][j], C[i - 1][j - 1]);
  }
}

bool ins(i64 x) {
  for (int i = m - 1; ~i; --i) {
    if (x >> i & 1) {
      if (!p[i]) return p[i] = x, 1;
      else x ^= p[i];
    }
  }
  assert(!x);
  return 0;
}

void gauss() {
  for (int i = 0; i < m; ++i) {
    if (p[i]) {
      assert(p[i] >> i & 1);
      for (int j = i + 1; j < m; ++j)
        if (p[j] >> i & 1)
          p[j] ^= p[i];
    }
  }
}

void dickdreamer() {
  std::cin >> n >> m;
  for (int i = 1; i <= n; ++i) {
    std::cin >> a[i];
    cnt += ins(a[i]);
  }
  prework();
  if (cnt <= (m + 1) / 2) {
    std::sort(p, p + m, std::greater<>());
    std::function<void(int, i64)> dfs = [&] (int x, i64 s) {
      if (!p[x]) return void(++res[__builtin_popcountll(s)]);
      dfs(x + 1, s);
      dfs(x + 1, s ^ p[x]);
    };
    dfs(0, 0);
  } else {
    gauss();
    for (int i = 0; i < m; ++i) {
      if (!p[i]) {
        q[i] = (1ll << i);
        for (int j = 0; j < m; ++j)
          if (p[j] >> i & 1)
            q[i] |= (1ll << j);
      }
    }
    std::sort(q, q + m, std::greater<>());
    std::function<void(int, i64)> dfs = [&] (int x, i64 s) {
      if (!q[x]) return void(++ct[__builtin_popcountll(s)]);
      dfs(x + 1, s);
      dfs(x + 1, s ^ q[x]);
    };
    dfs(0, 0);
    for (int i = 0; i <= m; ++i)
      for (int j = 0; j <= i; ++j)
        for (int k = 0; k <= m - i; ++k)
          inc(coef[i][j + k], 1ll * getop(j) * C[i][j] % kMod * C[m - i][k] % kMod);
    for (int i = 0; i <= m; ++i)
      for (int j = 0; j <= m; ++j)
        inc(res[j], 1ll * ct[i] * coef[i][j] % kMod);
    for (int i = 0; i <= m; ++i) res[i] = 1ll * res[i] * qpow(qpow(2, m - cnt)) % kMod;
  }
  for (int i = 0; i <= m; ++i) res[i] = 1ll * res[i] * qpow(2, n - cnt) % kMod;
  for (int i = 0; i <= m; ++i) std::cout << res[i] << " \n"[i == m];
}

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;
}

P6829. [IOI 2020] 植物比较(数据结构,倍增,adhoc,猜结论)

首先考虑 \(2k>n\) 怎么做。对于这类与大小有关的排列构造题,一个比较常见的思路是找最特殊的最值来构造。这题我们就先找到最大值的位置。

显然最大值只能在 \(r_i=0\) 的位置里选,我们把这些位置拿出来,称构成的集合是候选集合。那么对于一个 \(r_i=0\)\(i\),如果 \(j\in(i,i+k-1]\) 则说明 \(h_i>h_j\),又因为 \(2k>n\),所以候选集合中的任意两个数 \(i,j\) 一定都能比较出 \(h_i\)\(h_j\) 的大小关系。最大值 \(n\) 的位置也就是候选集合里距离前驱最远的点,每次把最大值 \(n\) 对其余 \(r_i\) 的贡献去掉后再做同样的事情即可。

然后再考虑 \(2k\leq n\) 且答案一定是 \(1\)\(-1\) 的情况。容易发现只需要构造出任意一个合法排列,这个时候候选集合里的点大小关系会构成若干条链,最大值的位置就不唯一了,最终的构造方式也会不唯一。由于只需要构造出任意一个排列,我们每次直接随便取一个链头做最大值即可,写一下会发现一定能构造出来,设构造出的排列是 \(p\)。询问的时候只需要判断 \(p_i\)\(p_j\) 的大小关系。

这里有个问题是为什么一定能构造出来?我们不妨先随便找一组合法的排列 \(h\),如果 \(j\in(i,i+k-1]\)\(h_j>h_i\) 就连一条 \(j\to i\) 的边,并把找最大值看成一个拓扑排序的过程,每次都会选择一个入度为 \(0\) 的点删掉,所以如果存在至少一组合法排列,这么做一定能构造出来。

观察上面的证明,会发现相距不超过 \(k\) 的两个点之间的大小关系是一定的,否则构造一定会出现矛盾。又因为我们跑拓扑排序的图上也只有距离不超过 \(k\) 的点连出来的边,所以所有能够确定的大小关系都可以通过刚才构造出来的排列来确定。把距离不超过 \(k\) 的点连上边后再跑传递闭包即可做到 \(O(n^3)\)

考虑优化。假设现在需要判断 \(h_x>h_y\),我们只需要找到任意一组 \(x=i_1\to i_2\to\ldots i_m=y\),使得 \(dis(i_j,i_{j+1})\leq k\)\(p_{i_j}>p_{i_{j+1}}\)。注意到这个序列不会出现一个子段是 \(a\to b\to c\)\(a<b>c\),否则一定可以调整成 \(a\to c\),所以走的过程一定没有回头路。

然后先枚举 \(x\)\(y\) 走的方向,假设每次往编号大的方向走。如果 \(dis(x,y)\geq k\),我们贪心地往 \(i\in[x,x+k-1]\) 且满足 \(p_x>p_i\)\(p_i\) 最大的 \(i\) 走是对的。证明考虑如果 \(j\) 满足条件且 \(j<i\),那么 \((x+k-1,y]\) 部分 \(j\) 能跳到的 \(i\) 也一定能跳到。如果 \(j>i\),则 \(i\) 能一步跳到 \(j\),所以先走到 \(i\) 一定不劣。

这么一直跳直到 \(dis(x,y)<k\) 就停止,这时再比较 \(p_x\)\(p_y\) 即可。跳的过程容易倍增维护。

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

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

#ifdef ORZXKR
#include "grader.cpp"
#endif

using pii = std::pair<int, int>;

const int kMaxN = 2e5 + 5;

int n, k;
int r[kMaxN], h[kMaxN], pre[kMaxN][19], nxt[kMaxN][19];

int getdis(int x, int y) { return x <= y ? y - x : y - x + n; }
int getnxt(int x, int d) { x += d; return ((x - 1) % n + n) % n + 1; }

struct SGT {
  std::pair<int, int> mi[kMaxN * 4];
  int tag[kMaxN * 4];
  void pushup(int x) { mi[x] = std::min(mi[x << 1], mi[x << 1 | 1]); }
  void addtag(int x, int v) { mi[x].first += v, tag[x] += v; }
  void pushdown(int x) {
    if (tag[x]) addtag(x << 1, tag[x]), addtag(x << 1 | 1, tag[x]), tag[x] = 0;
  }
  void build(int x, int l, int r, int *arr) {
    tag[x] = 0;
    if (l == r) return void(mi[x] = {arr[l], l});
    int mid = (l + r) >> 1;
    build(x << 1, l, mid, arr), build(x << 1 | 1, mid + 1, r, arr);
    pushup(x);
  }
  void update(int x, int l, int r, int ql, int qr, int v) {
    // if (x == 1) std::cerr << "update " << ql << ' ' << qr << ' ' << v << '\n';
    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);
  }
  pii query(int x, int l, int r, int ql, int qr) {
    if (l > qr || r < ql) return {1e9, 0};
    else if (l >= ql && r <= qr) return mi[x];
    pushdown(x);
    int mid = (l + r) >> 1;
    return std::min(query(x << 1, l, mid, ql, qr), query(x << 1 | 1, mid + 1, r, ql, qr));
  }
} sgt;

void construct() {
  std::set<int> st;
  std::set<std::pair<int, int>> stl;
  std::function<int(int)> getpre = [&] (int x) {
    auto it = st.lower_bound(x);
    if (it == st.begin()) return *st.rbegin();
    else return *--it;
  };
  std::function<int(int)> getnxt = [&] (int x) {
    auto it = st.lower_bound(x + 1);
    if (it == st.end()) return *st.begin();
    else return *it;
  };
  std::function<int(int, int)> getdis = [&] (int x, int y) {
    return x < y ? y - x : y - x + n;
  };
  std::function<void(int)> ins = [&] (int x) {
    if (!st.size()) {
      st.emplace(x), stl.emplace(n, x);
      return;
    }
    int pre = getpre(x), nxt = getnxt(x);
    stl.erase({getdis(pre, nxt), nxt});
    st.emplace(x), stl.emplace(getdis(pre, x), x), stl.emplace(getdis(x, nxt), nxt);
  };
  std::function<void(int)> del = [&] (int x) {
    if (st.size() == 1) {
      st.erase(x), stl.erase({n, x});
      return;
    }
    int pre = getpre(x), nxt = getnxt(x);
    st.erase(x), stl.erase({getdis(pre, x), x}), stl.erase({getdis(x, nxt), nxt});
    stl.emplace(getdis(pre, nxt), nxt);
  };
  sgt.build(1, 1, n, r);
  for (int i = 1; i <= n; ++i)
    if (!r[i])
      st.emplace(i), sgt.update(1, 1, n, i, i, 1e9);
  for (int i = 1; i <= n; ++i)
    if (!r[i])
      stl.emplace(getdis(getpre(i), i), i);
  // std::cerr << st.size() << ' ' << stl.size() << '\n';
  for (int i = n; i; --i) {
    // std::cerr << "fuck " << st.size() << ' ' << stl.size() << '\n';
    assert(st.size() && st.size() == stl.size());
    int x = stl.rbegin()->second;
    // std::cerr << x << '\n';
    h[x] = i, del(x);
    if (x < k) sgt.update(1, 1, n, 1, x, -1), sgt.update(1, 1, n, x - k + 1 + n, n, -1);
    else sgt.update(1, 1, n, x - k + 1, x, -1);
    assert(sgt.mi[1].first >= 0);
    for (; sgt.mi[1].first == 0;) {
      int p = sgt.mi[1].second;
      // std::cerr << "??? " << p << '\n';
      sgt.update(1, 1, n, p, p, 1e9), ins(p);
    }
  }
  // for (int i = 1; i <= n; ++i) std::cerr << h[i] << " \n"[i == n];
}

void prework() {
  static int tmp[kMaxN];
  std::fill_n(tmp + 1, n, 1e9);
  sgt.build(1, 1, n, tmp);
  std::vector<int> id;
  for (int i = 1; i <= n; ++i) id.emplace_back(i);
  std::sort(id.begin(), id.end(), [&] (int x, int y) { return h[x] < h[y]; });
  for (auto i : id) {
    if (i < k) pre[i][0] = std::min(sgt.query(1, 1, n, 1, i), sgt.query(1, 1, n, i - k + 1 + n, n)).second;
    else pre[i][0] = sgt.query(1, 1, n, i - k + 1, i).second;
    if (!pre[i][0] || h[pre[i][0]] >= h[i]) pre[i][0] = i;
    if (i > n - k + 1) nxt[i][0] = std::min(sgt.query(1, 1, n, i, n), sgt.query(1, 1, n, 1, i + k - 1 - n)).second;
    else nxt[i][0] = sgt.query(1, 1, n, i, i + k - 1).second;
    if (!nxt[i][0] || h[nxt[i][0]] >= h[i]) nxt[i][0] = i;
    sgt.update(1, 1, n, i, i, -h[i] - 1e9);
    assert(h[i] >= h[pre[i][0]] && h[i] >= h[nxt[i][0]]);
    pre[i][0] = getdis(pre[i][0], i), nxt[i][0] = getdis(i, nxt[i][0]);
  }
  // for (int i = 1; i <= n; ++i) std::cerr << pre[i][0] << ' ' << nxt[i][0] << '\n';
  for (int i = 1; i <= std::__lg(n); ++i) {
    for (int j = 1; j <= n; ++j) {
      pre[j][i] = std::min(n + 1, pre[j][i - 1] + pre[getnxt(j, -pre[j][i - 1])][i - 1]);
      nxt[j][i] = std::min(n + 1, nxt[j][i - 1] + nxt[getnxt(j, nxt[j][i - 1])][i - 1]);
    }
  }
  // std::cerr << "kk " << pre[getnxt(3, -pre[3][0])][0] << ' ' << pre[3][1] << '\n';
  // assert(pre[3][std::__lg(n)] == 0);
}

bool checkbig(int x, int y) { // h[x] > h[y]
  {
    int _x = x, d = getdis(y, x);
    for (int i = std::__lg(n); ~i; --i)
      if (pre[_x][i] <= d - k)
        d -= pre[_x][i], _x = getnxt(_x, -pre[_x][i]);
    if (d >= k && pre[_x][0] <= d) _x = getnxt(_x, -pre[_x][0]);
    if (getdis(y, _x) <= k - 1 && h[_x] >= h[y]) return 1;
  }
  {
    int _x = x, d = getdis(x, y);
    for (int i = std::__lg(n); ~i; --i)
      if (nxt[_x][i] <= d - k)
        d -= nxt[_x][i], _x = getnxt(_x, nxt[_x][i]);
    if (d >= k && nxt[_x][0] <= d) _x = getnxt(_x, nxt[_x][0]);
    if (getdis(_x, y) <= k - 1 && h[_x] >= h[y]) return 1;
  }
  return 0;
}

void init(int K, std::vector<int> R) {
  n = R.size(), k = K;
  for (int i = 1; i <= n; ++i) r[i] = R[i - 1];
  construct(), prework();
}

int compare_plants(int x, int y) {
  ++x, ++y;
  if (checkbig(x, y)) return 1;
  else if (checkbig(y, x)) return -1;
  else return 0;
}

P10656. [ROI 2017] 学习轨迹 (Day 2)(数据结构,单调栈,找性质)

首先我们枚举 \(b\) 中选的子段 \([l_2,r_2]\),那么 \(a\) 选的就是将 \(a\) 中所有值在 \(b_{l_2},b_{l_2+1},\ldots,b_{r_2}\) 中的数删掉后,剩余的序列中和最大的连续子段。这个东西是难以用数据结构维护的,因为 \(a\) 的最优子段用 \([l_2,r_2]\) 难以简单地刻画。

上面的做法做不了的原因是 \(a\) 中没有必须要选的点,导致一组 \([l_2,r_2]\) 就有很多种选 \(a\) 的方案,考虑找一个 \(a\) 的必选点。注意到 \(a\)\(b\) 分别选的和不能同时小于对应序列总和的一半,否则将方案调整成一个序列全选,另一个序列不选一定更优。也就是说 \(a\)\(b\) 中至少有一个序列选的和不小于总和的一半,不妨假设是 \(a\)。那么 \(a\) 的带权中点是必选的!

设这个必选点是 \(p\)。对于一个 \(b_i=a_x\),如果 \(x>p\),将 \(i\) 选入 \([l_2,r_2]\) 后对 \(a\) 的限制条件就是 \(r_1<x\);如果 \(x<p\),限制是 \(l_1>x\)。这时 \(l_1\)\(r_1\) 就独立了,且分别可以用区间 max/min 来刻画,直接单调栈维护限制即可。

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

对于这类难以直接维护的最优化找区间问题,可以通过找必选点,来让最终的可能区间只有一个,且左右端点的限制变得互相独立。

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

#define int int64_t

using pii = std::pair<int, int>;

const int kMaxN = 1e6 + 5;

int n, m;
int a[kMaxN], b[kMaxN], x[kMaxN], y[kMaxN], prex[kMaxN], prey[kMaxN];
int posa[kMaxN], posb[kMaxN], lim1[kMaxN], lim2[kMaxN], lim3[kMaxN], lim4[kMaxN];

struct SGT {
  pii mx[kMaxN * 4];
  int tag[kMaxN * 4];
  void pushup(int x) { mx[x] = std::max(mx[x << 1], mx[x << 1 | 1]); }
  void addtag(int x, int v) { mx[x].first += v, tag[x] += v; }
  void pushdown(int x) {
    if (tag[x]) addtag(x << 1, tag[x]), addtag(x << 1 | 1, tag[x]), tag[x] = 0;
  }
  void build(int x, int l, int r) {
    tag[x] = 0;
    if (l == r) return void(mx[x] = {0, l});
    int mid = (l + r) >> 1;
    build(x << 1, l, mid), build(x << 1 | 1, mid + 1, r);
    pushup(x);
  }
  void update(int x, int l, int r, int ql, int qr, int 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);
  }
} sgt;

std::tuple<int, int, int> solve(int n, int m, int *a, int *x, int *b, int *y, int *prex, int *prey, int *posa, int *posb, int *lim1, int *lim2) {
  static int stk1[kMaxN], stk2[kMaxN];
  int mid = std::lower_bound(prex + 1, prex + 1 + n, (prex[n] + 1) / 2) - prex;
  int top1 = 0, top2 = 0;
  sgt.build(1, 1, m);
  // std::cerr << mid << '\n';
  std::tuple<int, int, int> res = {-1e9, 0, 0};
  for (int i = 1; i <= m; ++i) {
    if (posa[b[i]] == 0 || posa[b[i]] < mid) lim1[i] = n;
    else lim1[i] = posa[b[i]] - 1;
    if (posa[b[i]] == 0 || posa[b[i]] > mid) lim2[i] = 0;
    else lim2[i] = posa[b[i]];
    sgt.update(1, 1, m, i, i, prex[lim1[i]] - prex[lim2[i]]);
    sgt.update(1, 1, m, 1, i, y[i]);
    for (; top1 && lim1[i] < lim1[stk1[top1]]; --top1) {
      sgt.update(1, 1, m, stk1[top1 - 1] + 1, stk1[top1], prex[lim1[i]] - prex[lim1[stk1[top1]]]);
    }
    for (; top2 && lim2[i] > lim2[stk2[top2]]; --top2) {
      sgt.update(1, 1, m, stk2[top2 - 1] + 1, stk2[top2], -(prex[lim2[i]] - prex[lim2[stk2[top2]]]));
    }
    stk1[++top1] = stk2[++top2] = i;
    auto pr = sgt.mx[1];
    res = std::max(res, std::tuple<int, int, int>{pr.first, pr.second, i});
    // std::cerr << pr.first << '\n';
    // std::cerr << lim1[i] << ' ' << lim2[i] << '\n';
  }
  return res;
}

void dickdreamer() {
  std::cin >> n >> m;
  for (int i = 1; i <= n; ++i) std::cin >> a[i], posa[a[i]] = i;
  for (int i = 1; i <= n; ++i) std::cin >> x[i], prex[i] = prex[i - 1] + x[i];
  for (int i = 1; i <= m; ++i) std::cin >> b[i], posb[b[i]] = i;
  for (int i = 1; i <= m; ++i) std::cin >> y[i], prey[i] = prey[i - 1] + y[i];
  auto res1 = solve(n, m, a, x, b, y, prex, prey, posa, posb, lim1, lim2);
  auto res2 = solve(m, n, b, y, a, x, prey, prex, posb, posa, lim3, lim4);
  if (prex[n] >= prey[m] && prex[n] >= std::get<0>(std::max(res1, res2))) {
    std::cout << prex[n] << '\n' << 1 << ' ' << n << '\n' << 0 << ' ' << 0 << '\n';
  } else if (prey[m] >= prex[n] && prey[m] >= std::get<0>(std::max(res1, res2))) {
    std::cout << prey[m] << '\n' << 0 << ' ' << 0 << '\n' << 1 << ' ' << m << '\n';
  } else if (std::get<0>(res1) >= std::get<0>(res2)) {
    auto [val, l, r] = res1;
    std::cout << val << '\n' << *std::max_element(lim2 + l, lim2 + 1 + r) + 1 << ' ' << *std::min_element(lim1 + l, lim1 + 1 + r) << '\n' << l << ' ' << r << '\n';
  } else {
    auto [val, l, r] = res2;
    std::cout << val << '\n' << l << ' ' << r << '\n' << *std::max_element(lim4 + l, lim4 + 1 + r) + 1 << ' ' << *std::min_element(lim3 + l, lim3 + 1 + r) << '\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;
}

CF2061I. Kevin and Nivek(最优化dp,决策单调性,分治,根号分治)

首先容易想到 dp,设 \(f_{i,j}\) 表示前 \(i\) 天,赢了 \(j\) 场的最小代价。那么对于 \(a_i=-1\),转移是 \(f_{i,j+[j\geq i-1-j]}\leftarrow f_{i,j}\);否则转移是 \(f_{i,j}\leftarrow f_{i-1,j},f_{i,j+1}\leftarrow f_{i,j}+a_i\)。因为这个 dp 一行的数没有任何性质,所以直接优化单行的转移是不可行的,我们只能尽量让多行一起转移。

假设我们要让 \(f_{l-1,*}\) 一起转移到 \(f_{r,*}\),如果 \([l,r]\) 中没有 \(-1\) 是好做的,此时就不关心每行的顺序了,直接预处理出 \(g_{k}\) 表示 \([l,r]\)\(a\) 中前 \(k\) 小值之和,转移就是 \(f_{r,i+k}\leftarrow f_{l-1,i}+g_k\)。加上 \(-1\) 的转移后,设区间内有 \(c\)\(-1\)。注意到所有 \(i-(l-1-i)\geq r-l+1\),其在 \([l,r]\) 内无论怎么操作 Kevin 的胜场数都会更多,所以可以直接转移 \(f_{r,i+c+k}\leftarrow f_{l-1,i}+g_k\)\(i-(l-1-i)\leq -(r-l+1)\) 也同理,由于 \(g\) 是上凸的,所以这两个转移具有决策单调性,可以分治做到单次 \(O(n\log n)\)

此时只剩下 \(O(r-l)\) 个状态,且构成一段区间,这部分直接暴力转移,时间复杂度是 \(O((r-l)^2)\)。两部分根号平衡一下,总复杂度是 \(O(n\sqrt{n\log n})\),听说这个就能过,不过还能更优。


考虑分治,设分治函数 \(\text{solve}(l,r)\) 表示将 \(f_{l-1,*}\) 转移到 \(f_{r,*}\),乍一看决策单调性部分的转移单次就要 \(O(n\log n)\),总共能分治的轮数非常有限。注意到决策单调性部分的复杂度只与初始状态数和段长有关,设初始状态数是 \(s\),那么单次转移的复杂度是 \(O((s+r-l)\log n)\),我们只需要控制 \(s\) 不要太多就行了!

具体地,我们将 \(\text{solve}(l,r)\) 的定义改成将 \(f_{l-1,*}\) 没有被转移出去的状态转移到 \(f_{r,*}\),一个状态转移出去后直接删掉。对于所有没被删掉的状态 \(f_{l-1,i}\),如果 \(|i-(l-1-i)|\geq r-l+1\),就通过决策单调性转移。转移完,继续递归 \(\text{solve}(l,mid)\)\(\text{solve}(mid+1,r)\)

显然将 \(l-1\to r\) 的转移做完后只会剩余 \(O(r-l)\) 个连续的状态,所以 \(\text{solve}(l,mid)\) 的初始状态就是 \(O(r-l)\) 个,而 \([l,mid]\) 的所有转移只会拓展 \(O(mid-l)\),所以 \(\text{solve}(mid+1,r)\) 的初始状态数还是 \(O(r-l)\)。也就是说 \(\text{solve}(l,r)\) 的初始状态数 \(s\)\(O(r-l)\) 级别,每次决策单调性转移的复杂度是 \(O((s+r-l)\log n)=O((r-l)\log n)\),总复杂度就是 \(O(n\log^2n)\)

对于这类单次转移无法优化的 dp 问题,可以考虑一次转移多行,再通过根号分治等算法优化成正解。

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

#define int int64_t

const int kMaxN = 3e5 + 5;

int n;
int a[kMaxN];
std::map<int, int> f[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); }

void upd(int x, int y, int w) {
  if (f[x].count(y)) chkmin(f[x][y], w);
  else f[x][y] = w;
}

void solve(int l, int r) {
  if (!f[l - 1].size()) return;
  if (l == r) {
    for (auto [x, v] : f[l - 1]) {
      if (a[l] == -1) upd(l, x + (x - (l - 1 - x) >= 0), v);
      else upd(l, x, v), upd(l, x + 1, v + a[l]);
    }
    std::map<int, int>().swap(f[l - 1]);
    return;
  }
  static int g[kMaxN] = {0}, id1[kMaxN], val1[kMaxN], id2[kMaxN], val2[kMaxN];
  int det = 0, cnt = 0, cnt1 = 0, cnt2 = 0;
  for (int i = l; i <= r; ++i) {
    if (a[i] == -1) ++det;
    else g[++cnt] = a[i];
  }
  for (auto [x, v] : f[l - 1]) {
    if (x - (l - 1 - x) >= r - l + 1) id1[++cnt1] = x, val1[cnt1] = v;
    else if (x - (l - 1 - x) <= -(r - l + 1)) id2[++cnt2] = x, val2[cnt2] = v;
  }
  std::sort(g + 1, g + 1 + cnt);
  for (int i = 1; i <= cnt; ++i) g[i] += g[i - 1];
  std::function<void(int, int, int, int)> trans1 = [&] (int ll, int rr, int ql, int qr) {
    if (ll > rr || ql > qr) return;
    if (ql == qr) {
      for (int i = ll; i <= rr; ++i) {
        int d = i - det - id1[ql];
        d = std::max<int>(d, 0);
        if (d <= cnt) upd(r, i, val1[ql] + g[d]);
      }
      return;
    }
    int mid = (ll + rr) >> 1, p = qr + 1, val = 1e18;
    for (int i = ql; i <= qr; ++i) {
      int d = mid - det - id1[i];
      d = std::max<int>(d, 0);
      if (d <= cnt) {
        int tmp = val1[i] + g[d];
        if (tmp < val) p = i, val = tmp;
      }
    }
    if (val != 1e18) upd(r, mid, val);
    trans1(ll, mid - 1, ql, std::min(p, qr)), trans1(mid + 1, rr, std::max(p, ql), qr);
  };
  std::function<void(int, int, int, int)> trans2 = [&] (int ll, int rr, int ql, int qr) {
    if (ll > rr || ql > qr) return;
    if (ql == qr) {
      for (int i = ll; i <= rr; ++i) {
        int d = i - id2[ql];
        d = std::max<int>(d, 0);
        if (d <= cnt) upd(r, i, val2[ql] + g[d]);
      }
      return;
    }
    int mid = (ll + rr) >> 1, p = qr + 1, val = 1e18;
    for (int i = ql; i <= qr; ++i) {
      int d = mid - id2[i];
      d = std::max<int>(d, 0);
      if (d <= cnt) {
        int tmp = val2[i] + g[d];
        if (tmp < val) p = i, val = tmp;
      }
    }
    if (val != 1e18) upd(r, mid, val);
    trans2(ll, mid - 1, ql, std::min(p, qr)), trans2(mid + 1, rr, std::max(p, ql), qr);
  };
  if (cnt1) trans1(id1[1] + det, id1[cnt1] + det + cnt, 1, cnt1);
  if (cnt2) trans2(id2[1], id2[cnt2] + cnt, 1, cnt2);
  for (int i = 1; i <= cnt1; ++i) f[l - 1].erase(id1[i]);
  for (int i = 1; i <= cnt2; ++i) f[l - 1].erase(id2[i]);
  int mid = (l + r) >> 1;
  solve(l, mid), solve(mid + 1, r);
}

void dickdreamer() {
  std::cin >> n;
  for (int i = 1; i <= n; ++i) std::cin >> a[i];
  for (int i = 0; i <= n; ++i) std::map<int, int>().swap(f[i]);
  f[0][0] = 0, solve(1, n);
  for (int i = n; i; --i) upd(n, i - 1, f[n][i]);
  for (int i = 0; i <= n; ++i) std::cout << f[n][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;
}

P11695. [JRKSJ ExR] 昼寝(数据结构,猫树分治,离线)

首先让所有的 \(r\leftarrow r-1\),变成闭区间。

如果只有一组询问,直接判断必须要用到倍增等算法,动态就更加难做,考虑猫树分治,将区间问题变为一个后缀和一个前缀拼起来的问题。

假设分治区间是 \([L,R]\),分治中点是 \(mid\),询问区间是 \((ql,qr,t)\)。则修改区间 \((l,r,tl,tr)\) 分为三种:被左区间 \([L,mid]\) 包含,被右区间 \([mid+1,R]\) 包含,以及横跨两个部分。先考虑被左区间包含的部分,显然我们只需要知道所有 \([ql,mid]\) 内所有符合 \(tl\leq t\leq tr\)\((l,r,tl,tr)\) 中,没被 \([l,r]\) 覆盖的最小的位置,因为最后一定是需要用跨过 \(mid\) 的区间来覆盖这些没被内部区间覆盖的位置,能覆盖最小的就一定能覆盖较大的。

现在考虑对于 \([ql,mid]\),怎么求出没有被覆盖的最小位置。容易想到一个暴力做法是设变量 \(p\) 表示目前已经覆盖了区间 \([ql,p]\),然后从 \(ql\) 扫到 \(mid\),假设扫到了 \(i\),如果 \(p<i-1\) 则说明最小的无法被覆盖的位置就是 \(i-1\),直接退出。否则如果存在一个修改区间 \((i,r,tl,tr)\) 使得 \(r\leq mid\)\(tl\leq t\leq tr\),就让 \(p\leftarrow\max(p,r)\)。如果这么扫到 \(mid\) 还没有退出,说明 \([ql,mid]\) 不存在没有被覆盖的位置。

优化则考虑将所有的询问一起做,先将询问按照时间 \(t\) 排序,在从左往右扫的过程中用一个线段树维护每个询问目前的 \(p\),如果扫到了一个询问的 \(ql\),就在线段树上激活这个询问。假设扫到了 \(i\),每次先在线段树中找到所有当前 \(p<i-1\) 且被激活的询问,将这些询问未被覆盖的最小位置更新为 \(i-1\) 后直接删除。加入一组修改区间 \((i,r,tl,tr)\) 时,由于询问已经按照 \(t\) 排好序了,所以修改就是区间 chkmax,用线段树维护即可。\([mid+1,qr]\) 的部分同理。

现在对于一组询问 \((ql,qr,t)\),我们已经知道了 \([ql,mid]\) 内部未被覆盖的最小位置 \(pl\) 以及 \([mid+1,r]\) 内未被覆盖的最大位置 \(pr\),则只需要判断是否存在一个修改区间 \((l,r,tl,tr)\),使得 \(ql\leq l\leq pl\)\(mid+1\leq r\leq qr\)\(tl\leq t\leq tr\)。这个东西和求 \(pl\) 的做法是类似的,还是从左往右扫,用线段树维护这样的区间中 \(r\) 的最小值,操作是每次激活一些位置以及区间 chkmin,最后扫到 \(pl\) 的时候单点查询,同样能用线段树维护。

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

对于这类无法直接做的区间问题,要多想想猫树分治。

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

// #define int int64_t

using pii = std::pair<int, int>;

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

int n, m;
int from[kMaxM], to[kMaxM];
bool res[kMaxM];

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 Query {
  int op, l, r, t;
} qq[kMaxM];

struct SGT {
  pii mi[kMaxN * 4];
  int tag[kMaxN * 4];
  void pushup(int x) { mi[x] = std::min(mi[x << 1], mi[x << 1 | 1]); }
  void addtag(int x, int v) { chkmax(mi[x].first, v), chkmax(tag[x], v); }
  void pushdown(int x) {
    if (tag[x] != -1e9) addtag(x << 1, tag[x]), addtag(x << 1 | 1, tag[x]), tag[x] = -1e9;
  }
  void build(int x, int l, int r) {
    tag[x] = -1e9;
    if (l == r) return void(mi[x] = {1e9, l});
    int mid = (l + r) >> 1;
    build(x << 1, l, mid), build(x << 1 | 1, mid + 1, r);
    pushup(x);
  }
  void update1(int x, int l, int r, int ql, int qr, int v) {
    if (l > qr || r < ql) return;
    else if (l >= ql && r <= qr) return addtag(x, v);
    pushdown(x);
    int mid = (l + r) >> 1;
    update1(x << 1, l, mid, ql, qr, v), update1(x << 1 | 1, mid + 1, r, ql, qr, v);
    pushup(x);
  }
  void update2(int x, int l, int r, int ql, int v) {
    if (l == r) return void(mi[x].first = v);
    pushdown(x);
    int mid = (l + r) >> 1;
    if (ql <= mid) update2(x << 1, l, mid, ql, v);
    else update2(x << 1 | 1, mid + 1, r, ql, v);
    pushup(x);
  }
  pii query(int x, int l, int r, int ql, int qr) {
    if (l > qr || r < ql) return {1e9, 1e9};
    else if (l >= ql && r <= qr) return mi[x];
    pushdown(x);
    int mid = (l + r) >> 1;
    return std::min(query(x << 1, l, mid, ql, qr), query(x << 1 | 1, mid + 1, r, ql, qr));
  }
} sgt;

void solve(int l, int r, std::vector<int> uid, std::vector<int> qid) {
  if (l == r) return;
  int mid = (l + r) >> 1;
  std::vector<int> ul, ur, um;
  std::vector<int> ql, qr, qm;
  for (auto i : uid) {
    if (qq[i].r <= mid) ul.emplace_back(i);
    else if (qq[i].l > mid) ur.emplace_back(i);
    else um.emplace_back(i);
  }
  for (auto i : qid) {
    if (qq[i].r <= mid) ql.emplace_back(i);
    else if (qq[i].l > mid) qr.emplace_back(i);
    else qm.emplace_back(i);
  }
  static int id[kMaxM], tl[kMaxM], tr[kMaxM], liml[kMaxM], limr[kMaxM];
  for (auto i : qm) liml[i] = mid + 1, limr[i] = mid;
  int t = 0;
  for (auto i : qm) id[++t] = i;
  std::sort(id + 1, id + 1 + t, [&] (int i, int j) { return i < j; });
  if (!t) {
    solve(l, mid, ul, ql), solve(mid + 1, r, ur, qr);
    return;
  }
  // for (int i = 1; i <= t; ++i) std::cerr << id[i] << " \n"[i == t];
  for (auto i : uid) {
    tl[i] = std::lower_bound(id + 1, id + 1 + t, from[i]) - id;
    tr[i] = std::lower_bound(id + 1, id + 1 + t, to[i] + 1) - id - 1;
    // int l = t + 1, r = 0;
    // {
    //   int L = 0, R = t + 1;
    //   while (L + 1 < R) {
    //     int mid = (L + R) >> 1;
    //     if (from[i] <= id[mid]) R = l = mid;
    //     else L = mid;
    //   }
    // }
    // {
    //   int L = 0, R = t + 1;
    //   while (L + 1 < R) {
    //     int mid = (L + R) >> 1;
    //     if (to[i] >= id[mid]) L = r = mid;
    //     else R = mid;
    //   }
    // }
    // tl[i] = l, tr[i] = r;
  }
  // std::cerr << "heige ";
  // for (int i = 1; i <= t; ++i) std::cerr << qq[id[i]].t << " \n"[i == t];
  for (auto i : qm) res[i] = 1;
  {
    static std::vector<int> vu[kMaxN], vq[kMaxN];
    sgt.build(1, 1, t);
    for (int i = l; i <= mid; ++i) vu[i].clear(), vq[i].clear();
    for (auto i : ul) vu[qq[i].l].emplace_back(i);
    for (int i = 1; i <= t; ++i) vq[qq[id[i]].l].emplace_back(i);
    for (int p = l; p <= mid; ++p) {
      for (; sgt.mi[1].first < p - 1;) {
        auto [v, i] = sgt.mi[1];
        liml[id[i]] = p - 1, sgt.update2(1, 1, t, i, 1e9);
      }
      for (auto i : vq[p]) sgt.update2(1, 1, t, i, p - 1);
      for (auto i : vu[p]) {
        // std::cerr << qq[i].l << ' ' << qq[i].r << '\n';
        sgt.update1(1, 1, t, tl[i], tr[i], qq[i].r);
        // std::cerr << "upd " << tl[i] << ' ' << tr[i] << ' ' << qq[i].r << ' ' << from[i] << ' ' << to[i] << '\n';
      }
    }
    for (; sgt.mi[1].first < mid;) {
      auto [v, i] = sgt.mi[1];
      // std::cerr << "fuck " << i << ' ' << v << '\n';
      liml[id[i]] = mid, sgt.update2(1, 1, t, i, 1e9);
    }
  }
  {
    static std::vector<int> vu[kMaxN], vq[kMaxN];
    sgt.build(1, 1, t);
    for (int i = mid + 1; i <= r; ++i) vu[i].clear(), vq[i].clear();
    for (auto i : ur) vu[qq[i].r].emplace_back(i);
    for (int i = 1; i <= t; ++i) vq[qq[id[i]].r].emplace_back(i);
    for (int p = r; p >= mid + 1; --p) {
      for (; -sgt.mi[1].first > p + 1;) {
        auto [v, i] = sgt.mi[1];
        limr[id[i]] = p + 1, sgt.update2(1, 1, t, i, 1e9);
      }
      for (auto i : vq[p]) sgt.update2(1, 1, t, i, -(p + 1));
      for (auto i : vu[p]) {
        sgt.update1(1, 1, t, tl[i], tr[i], -qq[i].l);
      }
    }
    for (; -sgt.mi[1].first > mid + 1;) {
      auto [v, i] = sgt.mi[1];
      limr[id[i]] = mid + 1, sgt.update2(1, 1, t, i, 1e9);
    }
  }
  {
    static std::vector<int> vu[kMaxN], vq[kMaxN];
    sgt.build(1, 1, t);
    for (int i = l; i <= mid + 1; ++i) vu[i].clear(), vq[i].clear();
    for (auto i : um) vu[qq[i].l].emplace_back(i);
    for (int i = 1; i <= t; ++i) vq[qq[id[i]].l].emplace_back(i), vq[liml[id[i]]].emplace_back(-i);
    for (int p = l; p <= mid + 1; ++p) {
      for (auto i : vq[p]) {
        if (i < 0) continue;
        // std::cerr << "modify " << i << '\n';
        sgt.update2(1, 1, t, i, -1e9);
      }
      for (auto i : vu[p]) {
        // std::cerr << qq[i].l << ' ' << qq[i].r << '\n';
        sgt.update1(1, 1, t, tl[i], tr[i], -qq[i].r);
        // std::cerr << "upd " << tl[i] << ' ' << tr[i] << ' ' << qq[i].r << '\n';
      }
      for (auto i : vq[p]) {
        if (i > 0) continue;
        i = -i;
        res[id[i]] &= (p == mid + 1 || -sgt.query(1, 1, t, i, i).first <= qq[id[i]].r);
        // std::cerr << "shabi " << i << ' ' << liml[id[i]] << ' ' << qq[id[i]].r << ' ' << -sgt.query(1, 1, t, i, i).first << '\n';
      }
    }
  }
  {
    static std::vector<int> vu[kMaxN], vq[kMaxN];
    sgt.build(1, 1, t);
    for (int i = mid; i <= r; ++i) vu[i].clear(), vq[i].clear();
    for (auto i : um) vu[qq[i].r].emplace_back(i);
    for (int i = 1; i <= t; ++i) vq[qq[id[i]].r].emplace_back(i), vq[limr[id[i]]].emplace_back(-i);
    for (int p = r; p >= mid; --p) {
      for (auto i : vq[p]) {
        if (i < 0) continue;
        // std::cerr << "modify " << i << '\n';
        sgt.update2(1, 1, t, i, -1e9);
      }
      for (auto i : vu[p]) {
        // std::cerr << qq[i].l << ' ' << qq[i].r << '\n';
        sgt.update1(1, 1, t, tl[i], tr[i], qq[i].l);
        // std::cerr << "upd " << tl[i] << ' ' << tr[i] << ' ' << qq[i].l << '\n';
      }
      for (auto i : vq[p]) {
        if (i > 0) continue;
        i = -i;
        assert(p == limr[id[i]]);
        res[id[i]] &= (p == mid || sgt.query(1, 1, t, i, i).first >= qq[id[i]].l);
        // std::cerr << "shabi " << id[i] << ' ' << limr[id[i]] << ' ' << qq[id[i]].l << ' ' << sgt.query(1, 1, t, i, i).first << '\n';
      }
    }
  }
  // std::cerr << l << ' ' << r << '\n';
  // for (auto i : qm) std::cerr << "??? " << qq[i].l << ' ' << qq[i].r << ' ' << liml[i] << ' ' << limr[i] << '\n';
  solve(l, mid, ul, ql), solve(mid + 1, r, ur, qr);
}

void dickdreamer() {
  static int buc[kMaxN] = {0};
  std::cin >> n >> m;
  std::vector<int> uid, qid;
  for (int i = 1; i <= m; ++i) {
    std::cin >> qq[i].op;
    if (qq[i].op == 1) {
      std::cin >> qq[i].l >> qq[i].r; --qq[i].r;
      from[i] = i, to[i] = m, uid.emplace_back(i);
      if (qq[i].l == qq[i].r) ++buc[qq[i].l];
    } else if (qq[i].op == 2) {
      std::cin >> qq[i].t;
      to[qq[i].t] = i - 1;
      if (qq[qq[i].t].l == qq[qq[i].t].r) --buc[qq[qq[i].t].l];
    } else {
      std::cin >> qq[i].l >> qq[i].r; --qq[i].r, qq[i].t = i;
      if (qq[i].l == qq[i].r) res[i] = (buc[qq[i].l] >= 1);
      else qid.emplace_back(i);
    }
  }
  solve(1, n, uid, qid);
  for (int i = 1; i <= m; ++i)
    if (qq[i].op == 3)
      std::cout << (res[i] ? 'Y' : 'N') << '\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-04 21:26  下蛋爷  阅读(29)  评论(0)    收藏  举报