IOI2025集训队互测 W2
Day4(20241022)
考场上想到了 T1 的 \(60\%\) 的前缀和 \(20\%\) 的后缀,但是中间那一步不会;T2 貌似是很厉害的背包结论,我不会,但是赛时数据被简单做法爆掉了,难过。
感觉三道题都是很厉害的题目,我懂得欣赏。但是 T3 卡我常,难过。
T1 Désive
首先我们考虑如何快速求出 \(\operatorname{xormex}\),发现我们可以在 Trie 树上 DP,某一个节点的 DP 值 \(f_i\) 如下:有一个子树是满的(也就是 \(f_{ls}=2^k\) 或 \(f_{rs}=2^k\)),那么令 \(f_i=f_{ls}+f_{rs}\);否则 \(f_i=max(f_{ls},f_{rs})\)。
或者,从另一个角度考虑,从每一个 Trie 树上的叶子节点开始向上跳父亲,每当遇到一个满的兄弟节点,就加上它的大小。所有数中最大的就是 \(\operatorname{xormex}\)。
然后套用莫队可以做到 \(O(2^nn\sqrt{m})\) 解决 \(o=1\)。
发现要求子区间权值和的问题,所以尝试使用离线扫描线,然后套用历史和。我们考虑在 \(r\) 指针逐渐移动的过程中,实时维护对于每一个左端点的 \(\operatorname{xormex}\)。
先考虑 \(a_i\) 互不相同的情况,我们仍然维护一个 Trie 树上的 DP,那么 Trie 树上的每一个节点都只会被填满依次。根据上面的 DP,我们知道每当其被填满依次,它就会影响一部分的 DP 值,具体的就等于它对面的子树大小,发现加起来是 \(O(n2^n)\) 的,可以接受。但考虑它还需要在往上跳的过程中可以和某些满的兄弟树合并,这一部分可以使用类似归并的方式使得每一次只增加一个修改。这样总共的新增 \(\operatorname{xormex}\) 数量只会有 \(O(2^nn)\) 个。
我们维护出上面每个情况需要用到的编号最小的点,那么需要做的操作就是前缀取 \(\max\),历史和。发现由于只有前缀取 \(\max\),所以序列是单调的,所以可以变成区间加区间历史和。
考虑如果有 \(a_i\) 相同,那么一个子树被填满的时刻可能不止依次,导致复杂度可能劣化到 \(O(2^{2n})\),但是我们考虑到,如果将这个子树填满是从 \(v\) 到了 \(v'\),我们实际上需要考虑的只是兄弟子树中所有时序在 \([v,v']\) 之间的 \(\operatorname{xormex}\) 方案。
由此,我们可以对每一个节点建立一个求解 \(\operatorname{xormex}\) 的 Trie 树,那么每一次取出了值之后,将时序最早的那一个点删掉。而修改一个点只会在 \(O(n)\) 个 Trie 树里面加入,所以维护这一部分的复杂度不会超过 \(O(2^nn^2)\),得到的修改只会有 \(O(2^nn)\) 次。
使用区间加历史和线段树,时间复杂度为 \(O(2^nn^2+mn)\)。
T2 分道扬镳
记 \(v=\max\limits_{i=1}^nv_i,w=\max\limits_{i=1}^nw_i\)。
我们先考虑如何处理无价值背包(转化题意后也就是 \(k=1\) 的部分分):
有 \(n\) 个物品,第 \(i\) 个物品的重量为 \(v_i\),问能够选择若干个物品,使得重量和为 \(m\)。
显然存在一个 \(O(n^2v)\) 的直接 DP 的方式,即 \(f_{i,j}\) 表示前 \(i\) 的物品重量和为 \(j\) 是否可行。
题解中给出了一个 \(O(nv)\) 的求解方式。
发现我们需要关注的部分是接近 \(m\) 的那一部分,所以我们尝试只维护重量在 \(x\in [m-2v,m]\) 这个范围内的状态,那么为了保持满足条件,我们需要在加入后面的物品的同时支持删除前面的物品。
由此,我们维护 \(g_{i,x}\) 表示前 \(i\) 个数,重量和为 \(x\) 的情况下,能够删除的编号最大的物品为多少。如果 \(g_{i,x}=-1\) 表示无法达到这个状态。
初始化就是找到最小的 \(p\) 满足 \(S=\sum\limits_{i=1}^pv_i\ge m-2v\),令 \(g_{p,S}=p\)。那么对于第 \(1\sim p\) 个物品,我们的策略是删去;对于 \(p+1\sim n\) 号物品,我们的策略是加入。那么 DP 转移如下:
- \(g_{i,x}\to g_{i+1,x}\)。
- \(g_{i,x}\to g_{i+1,x+v_{i+1}}\)。
- 枚举 \(j\in [1,g_{i,x}]\),\(j-1\to g_{i,x-v_j}\)。
发现除了最后一个转移,前面两个转移都是 \(O(nv)\) 的。而对于最后一个转移,我们观察到对于 \(j\in [1,g_{i-1,x}]\) 的部分,这个 DP 会在 \(g_{i-1,x}\) 处已经发生转移了,所以是不必要转移的,那么第三个转移的范围是 \(j\in (g_{i-1,x},g_{i,x}]\)。
显然 \(g_{*,x}\) 是单调递增的,那么转移的复杂度就是 \(g_{n,x}-g_{1,x}\le n\),那么复杂度就是 \(O(nv)\)。
现在我们来尝试解决原问题,发现值域很大,但是由于题意保证了 \(r-l\le k\),说明我们也可以尝试贴着上界进行 DP。
我们设计状态 \(g_{i,x,y}\) 表示前 \(i\) 个数,重量和为 \(x\),价值和为 \(y\) 的情况下,能够删除的编号最大的物品为多少。我们尝试把 \(y\) 控制在一个 \(O(k)\) 的区间 \([\alpha(x),\beta(x)]\) 之间。
发现求解 \(r\) 的方式如下:按照 \(\dfrac{w_i}{v_i}\) 从大到小排序,贪心的选择,最后一个物品如果塞不下就让他乘上一个系数 \(p\),使得其能够刚好加入。那么我们可以认为 \(r\) 是一个关于容量 \(m\) 的凸函数,也就是 \(\beta(x)\)。
如果我们把物品按照 \(\dfrac{w_i}{v_i}\) 排序,那么我们发现加入后面的物品,或者删掉前面的物品,只会使得和 \(\beta(x)\) 的差距越来越大,那么直接让 \(\alpha(x)=\beta(x)-k\) 就一定合法了。
这样的情况下,DP 转移和上面几乎一致,时间复杂度是 \(O(nvk)\) 的,使用滚动数组之后空间复杂度为 \(O(n+vk)\)。
T3 观虫我
我们可以使用 \(64\) 位的 unsigned long long
去压缩 \(a\) 数组的存储,可以优化一定的常数。
发现存在如下的三种做法:
- 在每一次翻转的时候,翻转所有基于 \(x\) 将任意个 \(0\) 变成 \(1\) 对应的位置。
- 在每一次查询的时候,查询所有 \(x\) 将任意个 \(1\) 变成 \(0\) 对应的位置。
- 在每一次反转的时候,翻转所有基于 \(x\) 将任意个 \(0\) 变成 \(1\) 对应的位置,查询所有 \(\bar{x}\) 将任意个 \(1\) 变成 \(0\) 对应的位置。
发现就是对于每一位的贡献矩阵 \(\begin{bmatrix}1&1\\0&1\end{bmatrix}\) 的三种拆分方式:\(\begin{bmatrix}1&1\\0&1\end{bmatrix}\times \begin{bmatrix}1&0\\0&1\end{bmatrix}\),\(\begin{bmatrix}1&0\\0&1\end{bmatrix}\times \begin{bmatrix}1&1\\0&1\end{bmatrix}\),\(\begin{bmatrix}1&0\\1&1\end{bmatrix}\times \begin{bmatrix}1&1\\1&0\end{bmatrix}\)。
发现每一位是独立的,那么我们就可以尝试找到每一种对于每一位分配方式 \(A,B,C\),使得需要进行的操作数最小。
对于翻转操作 \(x\),需要操作的位数为 \(|(A\cap \bar{x})\cup(C\cap x)|\);对于查询操作,需要操作的位数为 \(|(C\cap \bar{x})\cup(B\cap x)|\)。而某一种方案的操作数就是所有 \(2\) 的操作位数次方之和。题解中证明了随机选取 \(A,B,C\),期望操作数为 \(O(\left(\frac{4}{3}\right)^n)\) 的,加上 unsigned long long
的压位优化就可以做到 \(O(\left(\frac{4}{3}\right)^{n-\log \omega})\) 的复杂度。
但是很可惜我写的代码可能因为常数问题无法通过,是我不懂得欣赏了。
Day5(20241025)
今天 T1 发现神秘 \(O(n\log n)\) 做法,貌似暴标了。T2 感觉和正解有一定的距离,但是不远。
T1 长野原龙势流星群
发现有一个很显然的二分答案做法,我们先二分答案 \(X\),然后把 \(i\) 点的权值变成 \(w_i-X\),就是检验是否存在以这个点为根的权值和 \(\ge 0\) 的连通块了。这个问题是有一个很显然的贪心的的:\(f_i\) 表示以 \(i\) 为根的答案,那么就有 \(f_i=w_i-X+\sum\limits_{y\in son(i)}\max(f_v,0)\),可以认为是它只选择那些 \(>0\) 的儿子。
但是发现二分没有前途,所以我们考虑对于 \(X\) 进行扫描,直接从大到小,那么所有点的权值也都从 \(w_i-X\) 开始从小往大增大。
如果我们能在这个过程中动态维护所有的 \(f_i\),那么我们就只需要关注每一个 \(f_i\) 变成 \(0\) 的时刻的 \(X\) 即可。
而对于一个 \(f_i\),其变成 \(0\) 之后,随着 \(X\) 的增大,其必然一直保持 \(f_i>0\),那么在其父亲的决策中就必然会选择这个点。那么我们就可以让其和其父亲所在的连通块合并。
那么我们就变成了合并树上的两个连通块,以及查询当前所有 \(<0\) 的 \(f_i\) 中最早变成 \(0\) 的 \(f_i\)(也就是找最小的 \(\dfrac{f_i}{siz_i}\))。发现可以直接使用并查集和可删堆分别处理,时间复杂度 \(O(n\log n)\)。
T2 Classical Counting Problem
发现如果确定了 \(min\) 和 \(max\) 的具体值,这个连通块就是确定的:首先需要将 \(min\) 和 \(max\) 联通,然后依次将和当前连通块相邻的点 \(v\in[min,max]\) 的数加入连通块知道不存在为止。
记 \(S(x)\) 表示以 \(x\) 为最小值的极大连通块,\(T(x)\) 表示以 \(x\) 为最大值的极大连通块。那么如果 \(min\in T(max)\) 且 \(max\in S(min)\),那么意味着 \(min\) 与 \(max\) 联通,且连通块大小为 \(|S(min)\cup T(max)|\)。
而发现 \(S(x)\) 和 \(T(x)\) 的结构是可以被写成树形的,具体的,每一次选出连通块的最小/大值,然后将这个点删去,对每一个小连通块分别处理即可。
对于一个 \(min=u\),考虑我们要求什么:\(u\times \sum\limits_{u\in T(v),v\in S(u)}v\times (T(v) \text{中在} S(u) \text{子树内的节点数量})\)。
发现对 \(T\) 的构造树进行树链剖分,可以将求值变成如下数据结构问题:
- 单点修改 \(a_i\);
- 区间 \(b_i\pm 1\);
- 查询区间 \(a_i\times b_i\) 和。
这个是可以直接使用线段树维护的。而查询每一个 \(u\) 的答案,相当于要得到在 \(T\) 树上只点亮 \(u\) 子树内的所有节点的结果,这个部分是可以使用 DSU on tree 的。
最终复杂度为 \(O(n\log^3n)\)。
T3 运筹帷幄
会做法了,但是感觉写起来太困难了。
首先我们考虑如何对于一个点去求解答案。发现我们有如下贪心:dfs 整棵树,处理 \(x\) 前先依次处理它的每一个儿子,不断将 \(x\) 子树内最深的点移动到 \(x\) 直到其他子树没有节点或者 \(x\) 处被填满了。
那么尝试使用换根来实现对每一个点处理这个过程。我们尝试维护一个数组,其中 \(f_i\) 表示距离当前节点为 \(i\) 的棋子数量,那么每一次操作都可以抽象成取出后 \(k\) 大然后插到最前面。
考虑从一个点 \(x\) 到它的儿子的时候,发现轻儿子可以利用 DSU on tree 的结论保证复杂度,所以需要想一个办法处理重儿子。我们不妨单独将重儿子的那些点取出来,只需要维护这些点的后多少个被删掉了即可。
这样我们就得到了一个如下的做法:我们对于每一个点维护出数组 \(f_{x,i}\) 表示 \(x\) 子树内处理完之后距离 \(x\) 深度为 \(i\) 的点的数量。那么 \(x\) 的数组可以从重儿子 \(bson_x\) 处继承过来,轻儿子直接暴力合并即可。如果需要还原重儿子的数组,可以直接撤销。
接下来在换根的过程中,维护一个散点数组 \(F\),以及一个集合 \(S\),其中 \(S\) 就是存储 dfs 过程中加入的重儿子数组编号。
当我们访问 \(x\) 节点的时候,先将所有轻儿子的点加入 \(F\) 中。然后考虑继续遍历他的儿子 \(y\):
- 如果 \(y\) 是重儿子,就不需要进行任何操作;
- 如果 \(y\) 是轻儿子,就将重儿子对应的数组加入 \(S\) 中,然后将 \(F\) 中这个轻儿子子树的点删掉。
每一次要做取出后 \(k\) 大的操作时,可以直接在 \(F\) 和 \(S\) 中的数组上一起二分,由于 \(S\) 中只有 \(O(\log n)\) 个数组,所以复杂度是 \(O(n\log^2n)\) 的。
如果我们每一次二分只需要在 \(S\) 中的一个数组上进行,那么我们的复杂度就可以被优化到 \(O(n\log n)\)。
所以我们考虑每一次想 \(S\) 中加入数组的时候,只截取 \(>size_y\) 的部分,对于 \(\le size_y\) 的部分直接加入 \(F\) 的复杂度是正确的。
此时加入的数组区间必然是 \((size_y,size_x)\) 的部分,那么所有 \(S\) 中的数组都不相交,那么每次只需要二分其中一个即可。这样时间复杂度为 \(O(n\log n)\)。
Day6(20241027)
感觉今天的题比较简单。
T1 树数叔术
先考虑无标号树。
可以观察到,树上不可能存在两个权值为 \(0\) 的节点,因为如果这样对于其中任意一个点 \(rt\) 来说,\(S=\{rt\}\) 就不满足条件了。所以权值为 \(0\) 的点有且仅有一个,我们令其为根。
现在考虑只包含权值为 \(0,1\) 的点构成的虚树(那些由于构建虚树加入的点被认为无权值),其上如果存在两个连通块 \(S_1,S_2\)(不妨假设 \(|S_1|\le |S_2|\)),有 \(\operatorname{mex}(S_1)=\operatorname{mex}(S_2)=2\),那么显然有 \(x\in S_2\setminus S_1\) 使得 \(a_x\le 1\),那么就有 \(\min(E\setminus S_1)\le 1\),与题目要求矛盾。
发现这个结论可以任意拓展到任意的 \(0,1\dots i\) 构成的虚树。这也就意味着,每当我增加一个权值之后,虚树上满足 \(\operatorname{mex}\) 为 \(i+1\) 的连通块唯一,也就是整个虚树。
那么发现我们从 \(0,1\dots i-1\) 的树加入权值为 \(i\) 的节点的过程只能有两种情况:
- 所有的 \(i\) 都在虚树的树边上或是节点。
- 加入一个作为叶子的 \(i\)。
由此我们可以设计如下 DP,\(f_{i,j,k}\) 表示已经处理了权值为 \(0,1\dots i\) 的点,虚树大小为 \(j\),其中无权值点个数为 \(k\) 的方案数。
对于第一种转移,我们假设在树边上加入 \(V\) 个点,将 \(l\) 个无权点变成权值为 \(i+1\) 的,那么就有 \(f_{i+1,j+V,k-l}\gets f_{i,j,k}\times \dbinom{k}{l}\times \dbinom{V+j-2}{j-2}\),注意 \(V=0,l=0\) 以及 \(V\neq 0,j=0\) 的转移时不合法的,需要特判掉。发现 \(V\) 和 \(l\) 的转移是相对独立的,可以分别进行转移以节省复杂度。
对于第二种转移,我们发现这个叶子有两种情况:挂在某一个节点上,或在某一条虚边上加入一个虚点然后挂在这个虚点上。对于第一种情况,有转移 \(f_{i+1,j+1,k}\gets f_{i,j,k}\times j\);对于第二种情况,有转移 \(f_{i+1,j+2,k+1}\gets f_{i,j,k}\times (j-1)\)。
最终答案为 \(f_{V,n,0}\)。时间复杂度为 \(O(Vn^3)\),常数极小,可以直接通过。
T2 欧伊昔
发现这个问题和 FWT 很像,但是对于所有的 \(op\) 不一定都能像不进位加法来拆成三个数进行对位乘法。所以我们可以尝试拆成更多的数。
例如我们可以统计 \(0,1,2\) 出现的行数为 \(cnt_0,cnt_1,cnt_2\),那么我们有一个位数为 \(r=1+cnt_0+cnt_1+cnt_2-\max(cnt_0,cnt_1,cnt_2)\) 的方案。具体的,我们用一位存储所有的 \(9\) 个的答案,然后用 \(cnt_0+cnt_1+cnt_2-\max(cnt_0,cnt_1,cnt_2)\) 存储 \(cnt\) 较小的两个的结果。对列同理。
发现在数据随机的情况下,基本上有 \(r\le 5\),而 \(O(nr^n)\) 是一个可以接受的复杂度。
那么就可以直接通过了。
T3 人间应又雪
记从左端开始的操作为向左的,从右端开始的操作为向右的。
显然存在一个位置 \(x\),以其左侧的点为终点的操作都是向右的,以其右侧的点为终点的操作都是向左的。同时这个答案是可二分的。所以考虑二分答案 \(t\),我们假设有 \(j\) 个向右和 \(k\) 个向左的操作。
那么我们求出如下的四个数:\(pl_j,cl_j,pr_k,cr_k\)。其中 \(pl_j\) 表示,使用 \(j\) 个向右可以覆盖 \(1\sim pl_j-1\),但是无法覆盖 \(pl_j\),还剩下 \(cl_j\) 个向右没有使用。\(pr_k\) 和 \(cr_k\) 同理。
- 如果 \(pl_j<pr_k\),那么 \(pl_j\sim pr_k\) 无法覆盖,显然无解。
- 如果 \(pl_j>pr_k\),那么所有的位置都已经被覆盖过了。
- 如果 \(pl_j=pr_k=i\),如果有 \(a_i\le t+c(cl_j+cr_k)\),那么可以覆盖。
现在的问题就变成了如何求出 \(pl_j,cl_j,pr_k,cr_k\) 了,发现由于是对称的,所以我们先考虑 \(pl_j\) 和 \(cl_j\)。
对于某一对 \(j,k\) 我们考虑是要做什么。
初始的时候,每一个位置都被减了 \(t=k\),如果 \(a_i\le t\),那么可以直接跳过,不需要额外添加。
否则还需要添加 \(\left\lceil\dfrac{a_i-t}{c+1}\right\rceil\) 个向左才能补齐。
那么我们可以设计如下 DP,\(f_{i,k}\) 表示处理了前 \(i\) 位之后,有 \(k\) 个向左操作,会有 \(t=f_{i,k}\)。
那么有 \(f_{0,k}=k\),\(f_{i,k}=\begin{cases}f_{i-1,k}&,a_i\le f_{i-1,k}\\f_{i-1,k}+\left\lceil\frac{a_i-f_{i-1,k}}{c+1}\right\rceil&,a_i>f_{i-1,k}\end{cases}\)。
我们将 \(f_{i,k}\) 看作关于 \(k\) 的函数,也就是 \(f_i(k)\)。我们可以证明 \(f_i(k)\le f_i(k+1)\le f_i(k)+1\)。
由此,我们只需要找到所有位置 \(x\),满足 \(f_i(x)=f_i(x-1)+1\),同时维护 \(f_i(0)\),那么我们就可以确定每一个 \(f\) 的值了。
那么我们考虑转移,要先找到 \(a_i=f_{i-1}(x)\),其有 \(f_i(x)=f_{i-1}(x)=a_i\);但对于 \(f_{i-1}(x')=a_i-1\),有 \(f_i(x')=a_i-1+\left\lceil\frac{1}{c+1}\right\rceil=a_i\)。
那么 \(x\) 就会和 \(x'\) 合并,也就可以认为我们删掉了 \(x\) 这一个分界点。
同理,所有 \(f_{i-x}(x)=a_i-k(c+1),k=0,1\dots\),都会被合并。
然后,对于哪些 \(f_i(k)>t\) 的,我们就有 \(pl_{t-k}=i-1\),\(cr_{t-k}=t-f_{i-1}(k)\)。
由此,我们可以通过扫描一遍+树状数组二分来做到 \(O((n+m)\log m)\) 求解 \(pl\) 和 \(cl\)。
发现合并的过程是和 \(t\) 无关的,这也就意味着,我们可以预处理出来每一个位置会在什么时候被删去,然后我们只需要关注还剩下哪些数,那么我们每一次删去的时候就可以求出一段 \(pl\) 和 \(cl\) 的值了。
这个维护的容易的,但是有一定细节。
时间复杂度 \(O((n+m)\log m)\)。