划分 (Divide)

划分 (Divide)

题目描述

给定长度为 \(n\) 的序列 \(A\),接下来有 \(m\) 个操作需要你依次执行,操作类型有两种:

  • \(1\ x\ v\):将 \(A_x\) 改成 \(v\)。

  • \(2\ k\ B_1\ B_2\ ...\ B_k\):

如果可以将数列 \(A\) 分割成满足以下条件的区间集合,则输出 \(YES\),否则输出 \(NO\):

  • 区间两两不交、区间的并是 \([1,n]\) 且没有空区间。
  • 每个区间内所有数的异或结果必须在 \(B\) 序列中。
  • 若可以分割,请紧接着输出一种可行的区间分割方案。
    若你能保证你对于构造可行性的回答全部正确,且对于每次询问,你构造的区间个数必须小于等于 \(k^2(k+2)\),若能保证区间个数小于等于 \(k(k+2)\),即可得到全部分数,具体详见评分方式。

\[1\le n \le 10^5 \ \ \ \ 0\le A_i \le 2^{16} \ \ \ \ 0\le m \le 2\times 10^5 \ \ \ \ k\le 5 \]

简单情形的做法:

一次查询有一个显然的 \(DP\) 式子:

设 \(dp_{i}\) 表示前 \(i\) 个数的最小划分数目,设 \(s_i\) 表示前缀异或和,转移如下:

\[dp_i=\min_{k\in B}(T_{s_i\oplus k})+1 \]

其中 \(T_i\) 表示前缀异或和为 \(i\) 的位置的最小 \(dp\) 值。

一次查询时间复杂度 \(O(nk)\) 。修改可以暴力修改,不是瓶颈。

总时间复杂度就是 \(O(qnk)\) 。

只考虑查询:

\(k\le 5\)

由于 \(k\) 很小,所以单次查询应该是有一个什么 \(2^k\) 之类的时间复杂度。

由于划分是连续的且完整包含整个区间,所以每个分界点的前缀异或和一定是 \(B\) 中的若干个数异或起来的。而这些数总数不会超过 \(2^k\) 个。

这启示我们从这 \(2^k\) 个数的角度去思考问题。

不必求最小划分数:

题目没有要求划分的数量要最小。所以我们可以舍弃一下划分数量来换取时间上的高效率。

把划分看成跳跃,每次从 \(i\) 跳到 \(j\) 要求 $s_i\oplus s_j \in B $ 。

如果存在两个位置 \(i<j\) ,且 \(s_i=s_j\) 那么如果一个合法划分包含了 \([1,s_i]\) ,那么一定有一个合法区间能够包含 \([1,s_j]\) 。

也就是说只要能到 \(i\) , 那么就一定能够走到 \(j\) 。

但是走到 \(j\) 不一定能够走到 \(i\) 。

所以我们可以设 \(f_i\) 表示前缀异或和为 \(i\) 的能跳到的最前位置。

则可以类似 \(dij\) 的去转移 \(f\) 。具体地:

设 \(nxt[x][w]\) 表示位置 \(x\) 后面,前缀异或和为 \(w\) 的最前位置。

取出 \(f_i\) 最小的 \(i\) 。

那么可以枚举 \(k\in B\) 。

\[f_{i\oplus k}=min(f_{i\oplus k}, nxt[f_i][i\oplus k]) \]

容易发现这样不会出现循环反复互相松弛,所以跑一遍的时间复杂度是可控的。

查询时判断是否有 \(f_{s_n}<n\) 即可。反向构造也是容易的(最后一段右端点可以直接改成 \(n\) )。

由于一共划分的区间不会超过 \(2^k\) (不会互相松弛),在 \(k\le 5\) 的情况下,\(2^k\le k(k+2)\) ,所以构造合法。

加上修改:

唯一受修改影响的就是 \(nxt\) 数组,所以只要我们能快速处理 \(nxt\) ,这道题就做完了。

单点修改,在 \(s\) 中相当于是后缀加,导致有很多个 \(nxt\) 会同时改变。

这里记 \(g[i][j]\) 表示第 \(i\) 个块内前缀异或和值 \(j\) 出现的最小位置。

考虑分块:

  • 修改:整块打异或标记,散块暴力重构对应的 \(g\) 数组。

  • 查询:散块暴力找,整块查询 \(g\) 数组, 要注意打标记的影响。

posted @ 2026-10-29 08:57  wlhao  阅读(8)  评论(0)    收藏  举报