2025.4
一轮省集部分见这里。
QOJ5368 异世界的文章分割者
先考虑,如何计算一个给定串 \(s\) 的 \(g(s)\)。对 \(s\) 建 SAM,考虑 SAM 上的一个结点,求出它的 endpos 集合中的最小值和最大值 \(mn,mx\),并记 \([l,r]\) 表示这类子串的长度范围。发现它的贡献形式相当于是,对 \([mn,mx-r]\) 做区间加,对 \([mx-r+1,mx-l+1]\) 做区间加等差数列,可以使用差分完成,这样做到了 \(O(|s|)\) 算一次 \(g(s)\)。
对于原问题,考虑二分答案,每次切出来一个极长的代价 \(\le mid\) 的段出来。你注意到算一次代价是串长,双指针/二分/正常的倍增都需要撤销,所以单次 check 的复杂度会到 \(O(n^2)\) 或者 \(O(n^2\log n)\),显然不行。
考虑一种奇怪的倍增,假设当前要从 \(l\) 开始划分,从小到大枚举 \(i\),每次尝试让 \(r\) 增加 \(2^i\) 直到不合法,这样相当于找到了区间长度的 highbit,这段的长度被限制在了 \([2^i,2^{i+1})\),这时候在这一小段内再二分/倍增复杂度是对的,因为这样你找一段的复杂度是 \(O(L\log L)\) 而不是 \(O(n\log n)\) 了,其中 \(L\) 是本段长度。
最后的复杂度就是 \(O(n\log^2 n)\),如果认为 SAM 是线性的。
UOJ608 机器蚤分组
非常感性地看了一遍题解。
首先使用 dilworth 定理转化,这题要求的是最小链覆盖,转成最长反链就相当于是,选若干个子串使得之间没有一个串是另一个的子串。
然后这里有第一个观察是,最长反链上的子串肯定长度都相同,在脑子里感受下确实是很有道理的,具体的证明考虑调整法。因为长度相同,所以子串的限制就变成了相等,显然答案就是找到最小的长度 \(L\) 使得所有长度为 \(L\) 的子串互不相同,这个答案显然等于 \(|S|-\max_{i<j}\text{LCP}(S_{i:},S_{j:})\)。
考虑后缀的 \(\text{LCP}\) 就是后缀树上 \(\text{LCA}\) 的深度,显然固定 \(\text{LCA}\) 和 \(i\) 之后 \(j\) 肯定选后继,直接 dsu on tree 找支配对,一共 \(O(n\log n)\) 对,然后剩下的部分可以简单扫描线解决,复杂度 \(O(n\log^2 n)\)。
代码懒得写。
P11928 [PA 2025] 子序列 / Podciągi
首先显然变成所有本质不同的子序列减去恰好出现一次的子序列。
前者是好算的,直接设 \(f_{i,c}\) 表示前 \(i\) 个数当前结尾是 \(c\) 的本质不同子序列数量,因为字符集不大可以直接维护矩阵 ddp,这部分复杂度 \(O(nk^3\log n)\)。
对于后者,暴力的转移你是需要判 \(lst\) 大小关系的,这个直接 ddp 是没啥救的。考虑跳出 ddp 的思路,直接在线段树的分治结构上维护信息,每个结点记 \(f_{x,y}\) 表示区间内以 \(x\) 开头 \(y\) 结尾的本质不同子序列数量,考虑合并左儿子的 \(f_{x,a}\) 和右儿子的 \(f_{b,y}\) 时什么情况会算重,发现无非就是右儿子第一个 \(b\) 前有 \(a\) 或者左儿子最后一个 \(a\) 后有 \(b\)。
可以直接暴力合并,复杂度 \(O(nk^4\log n)\)。也可以每个结点额外维护一个矩阵表示合并的转移,这样这部分也可以 \(O(nk^3\log n)\)。
P11355 [eJOI 2023] Teleporters
很牛啊,我好像完全不会做。
两个人一起动显然是很麻烦的事情,考虑它们坐标的差值 \(x_1-x_2\),观察一下如果两个人分别选了位置在 \(c_i,c_j\) 的传送器,移动后两个人的差值会变成 \(x_2-x_1+2(c_i-c_j)\)。所以这里可以做出第一步转化了,初始我们在一个点 \(x_1-x_2\) 上,每次可以选一对 \((i,j)\) 花费 \(|f_i-f_j|\) 的代价移动到 \(2(c_i-c_j)\) 的对称点,求移动到 \(0\) 的最小代价。
考虑如果分别选了 \(p_1,p_2,\ldots,p_k\) 这些点进行对称,那么偏移量就是 \(2\sum_{i=1}^k(-1)^{k-i}p_i\),你注意到这里一个 \(p_i\) 是某个 \(c_i-c_j\),我们完全可以交换 \(i,j\) 实现取反,也就是这个式子里每一项的正负号可以随便定,根据裴蜀定理只要有 \(|x_1-x_2|\) 是偶数且所有 \(|c_i-c_j|\) 的 \(\gcd\) 是它的因数就一定有解了。
考虑取出来所有能用的传送器按任意顺序排列,那么你只需要求此时 \(c_i\) 的差分的全局 \(\gcd\) 就能判是否有解了,所以不妨把所有传送器按照 \(f_i\) 排序,这样每次询问能用的就是一个区间。回到原问题,考虑单次询问怎么做,可以二分答案,加入区间内 \(f\) 差值不超过 \(mid\) 的相邻对。多组询问呢?显然上个整体二分即可,使用线段树维护区间 \(\gcd\),复杂度两个 \(\log\)。
QOJ4998 Keyboard Queries
首先询问的两个区间如果长度不一样就是一定不可能,否则只会是未知或者一定,因为全 a 显然符合所有限制,所以只需要判一定的情况。
对于回文的限制,可以通过连边来表示一些位置要求相同,这样一定的情况就是区间内每个点连通块编号构成回文,可以哈希维护。看上去一次操作要连很多条边,但是显然 \(n\) 个点至多只有 \(n-1\) 条连边会导致连通块的合并,如果我们每次只进行有用的连边可以直接启发式合并,直接在区间内每次二分找到第一个编号不同的位置连边即可,复杂度 \(O(n\log^2 n)\)。
ABC304G Max of Medians
考虑二分答案 \(mid\),数出来最多有多少对可以 \(\ge mid\),那剩下要做的显然是个在 Trie 上贪心的过程。
定义函数 \(f(u)\) 表示在 \(u\) 子树内进行匹配的最大对数,分析下我们的策略是啥:
-
如果 \(mid\) 在当前位是 \(0\),显然如果选出来两个来自不同子树的数匹配一定满足条件,所以这时候就尽量先跨子树匹配。设 \(a=siz_{ls_u},b=siz_{rs_u}\),不妨假设 \(a\le b\),直接返回 \(a+\min(\frac{b-a}{2},f(rs_u))\) 即可,取 \(\min\) 是因为要先留出 \(a\) 个和左子树匹配。
-
如果 \(mid\) 在当前位是 \(1\),我们要求接下来的匹配必须来自不同的子树,而且这只是必要条件,更低位还会有其它的限制,这时候好像不好处理。
考虑大胆地再定义 \(g(u,v)\) 表示在 \(u,v\) 子树内进行匹配的最大对数,对于上面的情况我们直接递归到 \(g(ls_u,rs_u)\) 即可,而 \(g(u,v)\) 的策略也是类似的:
-
如果 \(mid\) 在当前位是 \(0\),显然是先进行 \(ls_u,rs_v\) 和 \(ls_v,rs_u\) 之间的匹配,然后如果 \(u,v\) 都有一个相同方向的儿子有剩余就继续预留一下递归即可。
-
如果 \(mid\) 在当前位是 \(1\),显然直接返回 \(g(ls_u,rs_v)+g(rs_u,ls_v)\)。
注意到上述的递归只会使 Trie 上每个点恰好被访问一次,所以 check 一次是 \(O(n\log V)\) 的,最后带上二分复杂度就是 \(O(n\log^2 V)\)。
CF1616H Keep XOR Low
好像和上题是一样的啊,真牛。
4.12 A
对所有串建出来 AC 自动机,则子串可以描述为某个前缀的 fail 树上的祖先。设 \(dp_i\) 表示考虑前 \(i\) 个串且以 \(s_i\) 结尾的最大价值,相当于是在 Trie 上走前缀的时候查一下 fail 树上祖先的最大 dp 值。考虑对 fail 树的 dfs 序建线段树,并改成子树 chkmax 单点查,复杂度 \(O(L\log L)\)。
4.12 B/CF936E Iqea
考虑如何快速计算两个点之间的距离。特殊性质 A 来说显然就是曼哈顿距离,而特殊性质 B 格子之间形成了一棵树,只需要查树上距离。对于一般的情况,注意格子不会出现包围的情况,所以你取出每一列的构成连续区间的格子,把每个区间视为一个点向相邻的区间连边仍然会得到一棵树,这样你求一下树上两点距离可以得到只考虑横坐标的情况下需要移动几步,纵坐标你就对每一行重新建树再算一下相加即可,这样显然是对的。
你考虑我们还有一个暴力是,对激活的格子跑一个多源的 bfs,这个跑一次的复杂度是严格的 \(O(n)\),和激活的格子数量无关。而上面又提供给了我们一个可以枚举每个激活格子算距离的暴力,很自然的想法就是拼起来平衡下复杂度。考虑定期重构,攒的询问不超过 \(B\) 时暴力计算和每个点的距离,到 \(B\) 个时就重新跑下 bfs,直接取 \(B=\sqrt n\) 就得到了个 \(O(n\sqrt n)\) 的做法。
妈的怎么在 CF 上过不去??其实这题还有点分树的 2log 做法,暂时还不太明白。
CF1712F Triameter
首先加边后直径肯定不超过原树的直径,所以答案上界是 \(O(n)\) 的。对于新图上两点 \(u,v\) 的最短路 \(D(u,v)\),求出 \(f_x\) 表示 \(x\) 离最近的 \(1\) 度点多远,则 \(D(u,v)=\min(dis(u,v),f_u+f_v+x)\),其中 \(dis(u,v)\) 是树上 \(u,v\) 之间的距离。
然后脑子里就在想一些奇怪的东西了,并没有战胜,可能会了个 \(O(nq\log n)\) 的东西,感觉过不去。
因为答案范围不大,考虑从 \(ans=0\) 开始不断扩展,枚举一个 \(u\) 判断是否存在 \(v\) 使得 \(D(u,v)>ans\)。首先 \(f_v>ans-f_u-x\),按照 \(f\) 排序后这样的 \(v\) 是一段后缀。其次 \(dis(u,v)>ans\),这个直接维护每个后缀对应点集的直径端点检查即可。
然后就做完了,写 \(O(n\log n)-O(1)\) LCA 的话最后的复杂度是 \(O(nq+n\log n)\)。
4.14 A
因为升级只会从低等级变成高等级,所以考虑倒着 dp,这样只需要关心当前等级有几个物品,就没有后效性了。
设 \(f_{i,j}\) 表示倒着考虑到第 \(i\) 个物品,当前等级为 \(l_i\) 的物品有 \(j\) 个的最大价值,先处理出 \(p_{i,j}\) 表示最大的 \(k<i\) 使得 \(p_k=j\)。首先枚举当前物品选不选,如果下一个物品等级还为 \(l_i\) 则应该转移到 \(f_{p_{i,l_i},j/j+1}\),否则枚举下一个物品的等级 \(x\) 转移到 \(f_{p_{i,x},j'}\)。注意到使得 \(j'\) 不为 \(0\) 的 \(x\) 只有 \(O(\log n)\) 个,假设等级为 \(x'\) 时 \(j'\) 达到了 \(0\),相当于要更新下前面所有 \(l_i\ge x'\) 的 \(f_{i,0}\) 的值,这个也是容易的,额外开个数组即可,赛时写了个没必要的 BIT。
复杂度应该是 \(O(n^2\log n)\) 的,我不知道能不能分析到 \(O(n^2)\)。
4.14 B/CF1510H Hard Optimization/FJOI2022 D1T1
显然区间包含关系形成了森林,考虑变成树上问题。对于每个点来说,假设它有 \(m\) 个儿子,那自己整个线段就会被分成 \(m+1\) 段,显然自己选择的线段要么在这 \(m+1\) 段空隙里(可能再向左右扩展一些),要么完全被某个子树内的线段包含。
假如一条线段左右两侧都要和其它的线段合并,那么它的端点对总长度是没有贡献的,只有左侧或者右侧的情况也是同理,也就是说一条线段 \(u\) 的贡献是,如果它右侧不扩展有 \(R_u\) 的贡献,左侧不扩展有 \(-L_u\) 的贡献,这样得到了一个比较方便的统计贡献的方式。对于 \(u\) 选择的线段被子树内某点包含的情况,我们可以简单地认为是 \(u\) 子树内要多填一条线段。可以根据以上的分析设计出 dp 状态:设 \(f_{u,i,0/1,0/1}\) 表示考虑 \(u\) 的子树,欠了子树外 \(i\) 条线段没填,子树左右两侧是否扩展的最大价值。
转移时要先合并子树,合并进来子树的左侧扩展情况要和已经合并进来子树的右侧扩展情况相同,否则不合法,同时再在合并过程中考虑 \(u\) 插入在某两棵子树中间的情况,合并完所有子树之后再考虑 \(u\) 插入在两侧的情况,进行一些讨论可以做到 \(O(n^2)\) 求出答案。
构造方案更复杂一些,我们需要记录最终方案中每棵子树合并到父亲之前的状态是啥,然后按照上面的决策进行模拟,总之也不算太难实现。
4.14 C/CF1066G Balls and Pockets
这题是人类???
开始想的时候考虑的是这个东西相当于把一个映射复合 \(k\) 次,然后考虑加速这个映射的计算过程,发现很不可行,巨大的值域也没啥倍增的可能性,然后就不会做了。
观察单独一个位置的移动没什么性质,考虑观察连续一段球的移动。在数轴足够远的地方选一个位置 \(L\),将 \([L,L+n-1]\) 内的小球染色,显然在某个小球到达 \(a_n\) 之前,每次移动后这 \(n\) 个被标记的小球都会整体往左移动 \(n\) 个单位。经过某些 \(a_i\) 后,一些小球会被删去,但是没删去的被染色的小球始终是连续的一段,直到最后一个被染色的小球掉到 \(a_1\) 的洞里。同时你发现每次染色小球构成的区间都是连续但不交的,所以可以得到本题最重要的性质:\([a_1,L+n-1]\) 内的所有小球都会在某个时刻恰好被某个染色的小球覆盖一次。
假如对于一次询问的 \(x\) 我们能求出它在 \(t\) 时刻被染色的小球 \(p\) 覆盖,那答案其实就是在 \(t-k\) 时刻小球 \(p\) 的位置(相当于对这个映射求逆再复合 \(t-k\) 次)。发现两个问题都是好做的,模拟区间移动的过程然后使用数据结构维护即可,复杂度 \(O(n\log n)\)。
4.15 A
枚举 \(f_{x,y}\) 后变为所有存在 \(a_i\lor a_j=k\) 且 \(a_i=x,b_j=y\) 的 \(k\) 的答案与 \(f_{x,y}\) 取 \(\max\),直接 FWT。
4.15 B
直接记 \(f_{u,i,x}\) 表示 \(u\) 子树内选了 \(i\) 个点且钦定 \(u\) 最终去点 \(x\) 的答案,转移第二维是树背包且第三维在合并一对点的时候只需要 \(O(n)\) 的复杂度转移,所以是 \(O(n^3)\) 的,可以通过。
4.15 C
假如问题是给定两个不太长的字符串 \(S,T\),多次问 \(T\) 的一个子串在 \(S\) 的一个子串内的出现次数,这个是个经典的问题,拼起来建 SA 之后变成二维数点,可以主席树做到在线 \(O(\log n)\) 一次查询,或者也可以离线树状数组。
特判掉 \(k=1\) 的情况之后,每个 \(F_k\) 显然都是 \(F_{k+1}\) 的前缀。我们可以找到一个最小的 \(p\) 使得 \(|F_{p}|\ge|S|\),并认为 \(A=F_p,B=F_{p+1}\),这样的好处是 \(A,B\) 的长度都不会比 \(S\) 小,所以 \(S\) 的某次出现一定是要么在一个完整的 \(A/B\) 内,要么是跨过一对相邻的 \(A\) 或 \(B\)。
我们不妨直接将 \(F_k\) 表示成若干个新的 \(A/B\) 拼接起来的形式,则 \(F_k\) 的一个子串 \([a,b]\) 可以看作是,一段 \(A/B\) 的后缀,拼上若干个完整的 \(A/B\),最后再拼上一段 \(A/B\) 的前缀。因为斐波那契增长速度是指数的,我们可以通过递归的方式求出 \(F_k\) 的第 \(a,b\) 个字符在 \(A/B\) 的哪个前后缀里,也容易算出它们之间有几个完整的 \(A/B\),同时也能统计出它们之间有几种 \(AB/BA/BB\) 的相邻对,然后就可以转化成最开始的问题,对 \(A,B,AB,BA,BB\) 分别与 \(S\) 拼一下建 SA 即可,复杂度 \(O(n\log n)\)。
应该还有更聪明的写法,这个其实比较唐。
CF2066E Tropical Season
感觉真不难啊/yun。
先进行一些基本的分析,首先第一步我们会选两个同样重的水桶,设它们的质量都为 \(k\),如果里面没有毒桶那现在质量 \(\le 2k\) 的桶我们都可以确定,显然最优的策略是先确定它们,然后把可以确定的质量上界累加。假如某一步剩下的桶都比当前确定的质量和 \(sum\) 要大,则我们只能找两个没确定且质量差不超过 \(sum\) 的桶,我们可以给轻的桶倒水来确定这两个桶。显然这就是最优策略,最后检查能确定的桶的数量是否不小于 \(n-1\) 即可。
然后这里直接模拟这个过程复杂度是对的,大概是 \(O(n\log^2 n)\) 的,但是这个做法太唐了,让我们看一个有趣点的做法。
考虑倍增值域分块,对每个 \(k\) 将大小在 \([2^k,2^{k+1})\) 内的数分为一块,这样一个显然的性质是只要你能确定某个块内的一个桶,那块内其它的桶也都能确定。给每个块开 multiset 维护块内的最小值和最小相邻差值,每次询问只需要从小到大遍历每个块检查即可,复杂度是 \(O(n\log n)\) 的,而且特别好写。
P5327 [ZJOI2019] 语言
首先有个特别唐氏做法,直接上树剖变成 \(O(n\log^2n)\) 次矩形加,然后直接做就是 \(O(n\log^3 n)\)。
考虑如何求一个点 \(u\) 能到达的 \(v\) 的个数,经过一些观察可以发现合法的 \(v\) 就是,所有经过 \(u\) 的路径端点的虚树上的点(算上跨过的原树上的点),也就是虚树边权和。虚树边权和的求法是,按照 dfs 序排序然后求出相邻的 LCA,那我们可以直接按照 dfs 序建线段树把它放上去维护,每条路径相当于路径上的线段树都要激活两个端点,直接树上差分+线段树合并即可。
写 \(O(n\log n)-O(1)\) LCA 可以做到 \(O(n\log n)\)。
AT_kupc2020_m Many Parentheses
什么螳臂题。
显然先把不存在长度为 \(2k\) 的括号序列的限制给容斥掉,现在需要做的是快速计算 \(f(n,m)\) 表示放 \(n\) 个总长为 \(2m\) 的合法括号串的方案数,结论是 \(f(n,m)=\binom{2m+n-1}{m}-\binom{2m+n-1}{m-1}\)。
证明可以考虑写出暴力 dp 式改成递推的形式,然后再坐标变换一下变成格路计数。或者也可以考虑构造双射,在前面添 \(n-1\) 个左括号,和这 \(n-1\) 个左括号匹配的右括号当成 \(n\) 个盒子的分割符,然后又是格路计数,同样可以推出来。
CF1517F Reunion
SnowTrace:感觉这题其实是 NOIP T2 的难度。
额至少当 T3 可能是很合适的?
首先想计数你肯定得先刻画下这个最大半径 \(R\),你发现你不太好直接判定一棵树的最大半径更不好直接对着 dp,所以可以考虑先枚举这个 \(R\)。当然固定答案为 \(R\) 其实也不太好做,那就再继续转化,考虑变成对每个 \(R\) 求 \(ans\ge R\) 的方案数求和,这样限制就好处理很多。
然后接下来这里我条件推的有些混乱,就遗憾离场了。
考虑什么时候会有 \(ans\ge R\),发现就是说存在一个白点满足它的 \(R\) 邻域内没有黑点,那换句话说其实就是每个黑点的 \(R\) 邻域并起来不能覆盖树上的所有点,这个判定方式是很优美的。考虑以此设计 dp,对于一棵 \(u\) 为根的子树来说,你发现如果 \(u\) 没被覆盖你只关心子树内最深的没被覆盖的点,而如果 \(u\) 被覆盖了你又只需要知道 \(u\) 子树外距离 \(u\) 多近的点同样会被覆盖,所以可以直接设 \(dp_{u,x}\) 表示:
- \(x\ge0\) 时,\(u\) 被覆盖且子树外和 \(u\) 距离不超过 \(x\) 的点也被覆盖。
- \(x<0\) 时,\(u\) 没被覆盖且子树内最深的没有被覆盖的点离 \(u\) 距离为 \(-x-1\)。
转移是容易的,做一次 dp 的复杂度显然相当于 \(O(n^2)\) 的树上背包,所以最后的复杂度是 \(O(n^3)\)。
AGC024E Sequence Growing Hard
神秘的,不会做的。
你考虑一个序列肯定是由上一个序列插入一个数得到的,我们不妨钦定一段相同的数每次在最后插入(否则会数重),那插入一个数 \(x\) 时为了满足字典序变大的限制显然只能找一个 \(<x\) 的数插在它前面。
然后还是不太会做,考虑做一些转化,用一棵树来描述插入过程:一个点为一个二元组 \((x,y)\) 表示第 \(x\) 次操作插入了数字 \(y\),根为 \((0,0)\),每次在哪个数前插入就把那个数对应的点当作父亲。很神奇地,这棵树和序列组形成双射,而且每个点的二元组满足一些偏序关系:父亲的 \(x,y\) 都严格小于儿子的。
然后只需要对这棵树 dp,设 \(dp_{i,j}\) 表示 \(i\) 个点且根的 \(y\) 为 \(j\) 的树的个数,枚举 \(x\) 最小的儿子的子树大小转移即可,可以前缀和优化到 \(O(n^3)\)。
妈的这个做法也太难想了,好像有个是人类一点的做法,抽空回来学习下。
CF1515H Phoenix and Bits
其实是板子题,只是实现起来需要动点脑子。
首先与操作可以转化成异或操作和或操作,所以只需要解决异或和或即可。儿异或操作相当于是某些层要交换左右儿子,直接打标记处理即可。所以问题的重点就是或操作如何处理。
或操作要干的事其实也不复杂,相当于是对于一些层的点,如果它只有左儿子就把它换成右儿子,如果同时有左右儿子就把左儿子合并到右儿子上去。你分析下合并的复杂度是什么,其实就是删掉左右儿子子树重合的点,所以你把势能看作是当前的点数,这个势能只会下降,而初始的势能显然是 \(O(n\log V)\) 的,所以如果你每次能快速找到要合并的子树去做复杂度是很对的。
所以做法也就很明确了,你可以把要操作的区间对应的线段树先分裂出来,然后遍历线段树的过程中,如果子树内没有任何要修改的层存在有两个儿子的点,那操作其实就是子树内某些层左儿子换成右儿子,直接打上面的异或标记。否则你就继续找下去,碰到有两个儿子的点直接合并就好了。遍历完之后,把分裂出来的线段树合并回原树即可。
显然这样定位到一棵需要合并的子树是 \(O(\log V)\) 的,那结合上面的分析这题最后复杂度就是 \(O(n\log^2 V)\) 的,代码不长但是写起来很需要脑子。
4.18 A
先考虑解决贪心部分的问题,考虑邻项交换,得到 \(x\) 可以放在 \(y\) 前面的条件是 \(\min(a_x,b_y)<\min(a_y,b_x)\)。但是要注意我们不能按照这个排序,因为这不是个全序关系,正确的姿势是按照 \(a_i<b_i\) 和 \(a_i\ge b_i\) 分类,前一类按照 \(a_i\) 排序,后一类按照 \(b_i\) 排序,然后再拼接,这样任取两个位置总是满足条件。
对于求时间的问题,确定顺序后可以直接 ddp。所以你就先拉出所有人排个序,重编号一下再线段树维护一遍即可,复杂度 \(O(n\log n)\)。
4.18 B
【模板】李超树。
4.18 C/CF1043G Speckled Band
首先答案不超过 \(4\),然后是无聊的逐个判断环节。
无解当且仅当不存在两个相同字母,则区间长度大于 \(26\) 一定有解,否则扫一遍判即可。
\(ans=1\) 相当于判断是否存在循环节,本题范围不太大,可以直接枚举区间长度的因数然后 SA 求后缀 LCP 判断。
\(ans=2\) 有三种情况,形如 AAB ABB 和 ABA。前两个相当于是前缀或者后缀有平方串,使用优秀的拆分的技巧,求出每个位置开始/结尾的最短平方串长度即可。第三种相当于判是否存在 border,考虑判最小的 border(因为有个性质是最小的 border 在整个串的出现都是不相交的,否则有更小的 border),直接根号分治:长度不超过 \(\sqrt n\) 的 border 枚举一下 SA 判,否则枚举 \(rk_l\) 周围 \(\sqrt n\) 个后缀判(因为此时 border 出现次数不会超过 \(\sqrt n\))。
\(ans=3\) 时,我们有 \(s_l\not= s_r\),否则答案一定不超过 \(2\)。如果中间存在和 \(s_l\) 或 \(s_r\) 相同的字符那就合法,否则只会是 ABBC 这种情况,相当于判中间有没有个平方串。我们前面已经求出了每个位置开头的最短平方串长度,算出对应的结尾位置每次检查直接 ST 表查区间最小值即可。
否则就是 \(ans=4\)。
复杂度 \(O(n\log n+q\sqrt n)\),其实可以做到两个 \(\log\),但是我不会 border 理论那套。
ARC138F KD Tree
完全不会做,呜呜。
直接的想法是设 \(dp_{l,r,d,u}\) 表示矩形 \(x\in[l,r],y\in[d,u]\) 内的点能形成的点列数量,直接枚举分界线把劈开的两部分乘起来转移,但这显然会算重。
tzk_wk 的两句话总结得好:
考虑有以下两种一般性的思路:
探究一下什么序列能够被得到,由此而不是由操作序列的角度设计 DP 状态。
硬是从操作序列的角度入手,不过给操作序列定个顺序(比方说,字典序最小的操作序列),以保证任意一个合法的序列只会被算一次。
本题的思路就属于后者。对于每一种点列,我们让他被字典序最小的操作顺序统计,显然这形成了一种一一对应的关系。这里操作之间的大小关系比较方法是,\(x(1)<y(1)<x(2)<\ldots<x(n)<y(n)\),其中 \(x(i)\) 表示以横坐标第 \(i\) 小的点为竖线进行划分,\(y(i)\) 同理。
设 \(fx_i\) 表示第一次操作是 \(x(i)\) 且字典序最小的操作序列个数,直接转移还是不方便,我们考虑容斥,用竖线两边的方案数乘起来减去字典序更小的(这里我们只需要减去第一步更小的,后面的递归到子问题解决):
- 如果第一步是某个 \(x(j),j<i\),则多算的方案其实就是平面被划分出的三个矩形分别乘起来的方案。
- 如果第一步是某个 \(y(j),j<i\),则平面会被划分出四个矩形,显然右下角的区域不能有点,则此时多算的方案其实是左下到右上三个矩形顺次乘起来的方案。
同理定义 \(fy_i\),转移也是类似的。
综上我们以 \(O(n^6)\) 的复杂度解决了本题。
CF1842G Tenzing and Random Operations
这题也好,感觉用到的 trick 我其实可能都见过,但我还是被击杀了。
显然期望是假的,直接算所有情况的答案和除以 \(n^m\) 即可。考虑暴力的 dp,设 \(dp_{i,j}\) 表示前 \(i\) 个数用了 \(j\) 次操作的答案,转移有
直接做复杂度是 \(O(n^3)\) 的,然后好像直接对着这个式子大力上 GF 推就能推出正解了,但是我完全不会啊,倒闭了。
下面是优美的组合意义做法。考虑答案的式子形如 \(\prod (a_i+v+\ldots)\),这种东西有个经典的组合意义就是相当于你从每个括号里选一个数,贡献是每个括号里选的数乘起来。设 \(dp_{i,j}\) 表示前 \(i\) 个数已经钦定了 \(j\) 次操作,转移时考虑第 \(i\) 个括号选了啥:
- 选了 \(a_i\),直接贡献就行,\(dp_{i-1,j}\times a_i\to dp_{i,j}\)。
- 选了某个钦定过的 \(v\),\(dp_{i-1,j}\times j\times v\to dp_{i,j}\)。
- 选了没钦定过的 \(v\),这时候要钦定上,\(dp_{i-1,j}\times(m-j)\times i\times v\to dp_{i,j+1}\)。
核心思想就是,你暂时没用到的操作先不去决定它们,用到的时候再确定。
统计答案时是 \(\sum f_{n,i}\times n^{m-i}\),复杂度 \(O(n^2)\)。
ARC157F XY Ladder LCS
考虑暴力的 dp,可以设 \(dp_{i,j,S}\) 表示考虑 \(s[1,i]\) 和 \(t[1,j]\) 且 \(t(j,i]\) 部分的交换情况为 \(S\) 的 LCS(不妨认为 \(j<i\)),转移按照正常的 LCS dp 转移即可,复杂度 \(O(2^nn^2)\),肯定过不去。
可以感受到的是这个答案其实是很大的,其实我们有结论:答案至少为 \(\frac{2}{3}n\),考虑将串每三个一组分开,每组内任何情况都至少能贡献 \(2\) 的答案。证明直接分讨或者爆搜即可。这个可以再导出另一个性质,假设一组 LCS 中 \(s_i\) 和 \(t_j\) 进行了匹配,则 \(|i-j|\le\frac{1}{3}n\),否则答案就会小于下界。因此第三维的状态数是 \(O(2^\frac{n}{3})\) 的,最后复杂度是 \(O(2^{\frac{n}{3}}n^2)\),可以通过。
ARC174E Existence Counting
这不是普及题吗??
要求字典序小于给定序列,显然的想法是考虑枚举 LCP,然后钦定下一位填一个更小的数。假如钦定第 \(i\) 个数填一个小于 \(P_i\) 的数,设还剩下 \(a\) 个可以填的,那前缀确定的数答案会增加 \(a\binom{n-i}{k-i}(k-i)!\),\(a\) 个可以填的数答案会增加 \(\binom{n-i}{k-i}(k-i)!+(a-1)\binom{n-i-1}{k-i-1}(k-i)!\),而剩下不能填的数答案会增加 \(a\binom{n-i-1}{k-i-1}(k-i)!\)。
发现系数和具体填啥数基本完全无关,\(a\) 显然可以树状数组算,每次更新答案就是对已经确定的数加一个数,去掉被确定的位置后值域上的前后缀加一个数,显然随便维护,复杂度 \(O(n\log n)\)。
ARC186F Up-Down Queries
注意到一点是操作过程中 \(y\) 始终是单调不降的,所以可以考虑维护差分。类似 slope trick,维护一个可重集 \(S\),里面塞入 \(y_{i+1}-y_i\) 个 \(i\),则一次操作可以看成是加入两个 \(a_i\),然后是差分数组第一个值大于 \(0\) 的地方要减 \(1\),这个显然是弹出最小值。
考虑答案怎么算,首先忽略掉取 \(\max\) 的操作答案是 \(\sum_{i=1}^n(m-2a_i)\),而取 \(\max\) 操作相当于是我们弹出的位置对应的前缀不需要减 \(1\),所以每次答案再加上弹出的数即可。
还是不太好做,考虑把问题建立费用流模型。先建立 \(n\) 个点表示 \(n\) 次操作,然后每个点 \(i\) 连上 \(S\to i\),流量为 \(2\) 费用为 \(a_i\),表示第 \(i\) 次操作要加入两个 \(a_i\);每个 \(i<n\) 连边 \(i\to i+1\),流量为 \(\infty\) 费用为 \(0\),表示第 \(i\) 次操作没被弹出的数继续保留;再对每个 \(i\) 连边 \(i\to T\),流量为 \(1\) 费用为 \(0\),表示每次弹出一个最小值。
然后问题变成了在一张主体为一条链的图上模拟费用流,这部分大概和联合省选 2023 的 D1T3 很像。我们称一个点是满的当且仅当它子树内流入的点数等于子树大小(当然这里是一条链),加入一个数时如果祖先都没满那放心加入即可,否则找到最靠右的满点,与其子树内加入的数中的最大值进行比较,如果当前数更优就替换掉即可。
修改怎么办?相当于加删一条从 \(S\) 出发的边,删除不好做直接上线段树分治即可,复杂度 \(O(n\log^2 n)\)。
P10198 [USACO24FEB] Infinite Adventure P
好难啊,晕。
看到这种从某个点出发跳非常多步的,感受一下发现似乎只能通过类似倍增的方法解决。
直接记录时刻模当前 \(t_i\) 的值倍增是做不了的,你考虑如果跳到了一个 \(j\) 满足 \(t_j>t_i\) 就倒闭了。如何补救呢?你发现不同的 \(t_i\) 只有 \(O(\log n)\) 种,所以你可以跳到更大的位置就停下来,也就是设 \(f_{i,j,k}\) 表示当前在点 \(i\),时刻 \(\bmod\ 2^{t_i}=j\),走了 \(2^k\) 步跳到了哪(中间有 \(t\) 更大的就停),并设 \(g_{i,j,k}\) 表示其对应的步数。
但是这个倍增预处理要三个 \(\log\)。
考虑它复杂度高的原因是什么,问题在于我们下一步可能跳到一个 \(t\) 很小的点,然后重新往高跳很费时间,我们希望每次跳到路径的前缀最大值。直接再设计个新的状态,设 \(f_{i,j,k}\) 表示从 \(i\) 出发,当前时间为 \(j\),途径了 \(2^k\) 个和 \(t_u=t_i\) 的 \(u\) 后跳到了哪,同样遇到 \(t_u>t_i\) 时停下,\(g_{i,j,k}\) 表示对应步数。
这样预处理和询问都是两个 \(\log\) 的复杂度。
CF1787G Colorful Tree Again
怎么这么唐。
修改一个点时,如果一条路径非 LCA 处穿过了它,这种路径显然唯一,直接修改它。否则把路径按照 LCA 分类摊开,这样每次影响的就是一段区间,然后直接上线段树维护就是 \(O(n\log n)\) 的。
CF1464F My Beautiful Madness
既然只需要判存在,当然是考虑判断最可能在交集中的点是否在所有路径的 \(d\) 邻域内。这个点存在吗?其实是有的,就是当前路径里深度最深的 LCA 的 \(d\) 级祖先,证明是不难的,记它为 \(u\)。
问题转化为判断一个点是否在所有路径的 \(d\) 邻域内。再找到 \(u\) 的 \(d\) 级祖先 \(v\),那首先所有的路径必须和 \(v\) 的子树有交,容易树状数组+树上差分判断。对于 LCA 在 \(v\) 子树外的路径,它们一定合法,因为一定经过 \(v\)。否则,只需要找 \(v\) 子树内离 \(u\) 最远的 LCA 判断距离 \(u\) 是否不超过 \(d\) 即可,经典地维护直径端点。
复杂度 \(O(n\log n)\)。
P11714 [清华集训 2014] 主旋律
约定:记 \(E_{S,T}\) 表示边的两端分属点集 \(S,T\) 的边数,特别地 \(E_{S,S}\) 表示点集 \(S\) 内的边数。
整张图强连通的限制太强了,考虑补集转化,用总数减去不强连通的方案数,而整张图不强连通相当于是,缩点后得到一张点数大于 \(1\) 的 DAG。现在我们已经把限制刻画成了一个相对好看的形式。
设 \(f_S\) 表示点集 \(S\) 强连通的方案数,用总数 \(2^{E_{S,S}}\) 减去不强连通的方案数。类似上面的 DAG 容斥,计算不强连通的方案数时,我们枚举将 \(S\) 缩点后最外层入度为 \(0\) 的点有几个,以及是由哪些点缩成的,得到:
其中 \(g_{S,i}\) 表示将 \(S\) 内的点缩成 \(i\) 个之间没有连边的单点的方案数。
考虑 \(g_{S,i}\) 如何计算,可以枚举 \(S\) 内编号最小的点所在的 SCC 构成的点集 \(T\),有:
这里有个可能的问题是 \(g_{S,1}=f_{S}\) 所以没法转移,但是我们发现一件事情就是 \(g_{S,1}\) 表示的就是 \(S\) 内强连通的方案数,我们在转移 \(f_{S}\) 的时候要减去不合法的方案数,所以转移时如果 \(T=S\) 且 \(i=1\) 则不能转移,这样 \(f_{S}\) 转移时恰好用不到 \(g_{S,1}\),转移完后再将 \(g_{S,1}\) 赋值为 \(f_{S}\) 即可。
这样直接按照上面的式子做就可以做到 \(O(3^nn)\)。
能不能再优秀一些呢?答案是可以的,考虑重新定义 \(g_{S}\) 表示将 \(S\) 内缩成若干个单点的带容斥系数的方案数,也就是缩成奇数个点的方案数减去缩成偶数个点的方案数,那么 \(f_S\) 的转移可以写成:
而 \(g_S\) 的转移同样是枚举 \(S\) 内编号最小的点所在 SCC 的点集,得到:
同样 \(f_S\) 的转移实际上用不到 \(g_S\),所以复杂度优化到了 \(O(3^n)\)。
P10221 [省选联考 2024] 重塑时光
首先考虑概率转计数,计算将 \(1,2,\ldots,n\) 划分成 \(k+1\) 个可以为空的点集,点集内部构成其导出 DAG 的一个拓扑序,点集之间也构成 DAG 的方案数,最后再除以总数 \(n!\binom{n+k}{k}\) 即可。
如果排列已经完成了划分,不考虑段之间的限制,每段内部的排列顺序要构成这段点集导出 DAG 的一个合法拓扑序,这个是容易的,直接设 \(h_S\) 表示点集 \(S\) 的合法拓扑序数量,枚举拓扑序第一个数是啥转移即可。
现在考虑段之间的限制,发现就是把一个段缩成一个点后要连成一个 DAG。设 \(f_{S,i}\) 表示将 \(S\) 内的点分成了 \(i\) 个段且段之间连成 DAG 的方案数(这里是无序的,也就是本质相同的 DAG 只算一次),类比上面的 DAG 容斥转移,枚举最外层有几段以及这些段的点集:
其中 \(g_{S,i}\) 表示将 \(S\) 内的点划分成 \(i\) 个之间没有连边的段的方案数,它的转移也是容易的:
考虑统计答案,设全集为 \(U\),则 \(f_{U,i}\) 对答案有 \(i!\times\binom{k+1}{i}\) 的贡献,分别表示把 \(i\) 个段进行排列和选择位置进行裁剪的方案数。
然后就做完了,复杂度 \(O(3^nn^2)\),瓶颈在 \(f_{S,i}\) 的转移,因为跑不太满所以可以通过。
事实上还可以进一步优化,你注意到 \(f_{S,i}\) 的转移相当于是和 \(g_{S,i}\) 第一维做子集不交并卷积,第二维做加法卷积。考虑把它们写成多项式的形式,设 \(F_S(x)=\sum_{i=0}^n f_{S,i}x^i\),\(G_S(x)=\sum_{i=0}^ng_{S,i}x^i\),转移实际上可以看成:
考虑使用点值表示法,将多项式代入 \(x=1\sim n+1\) 共 \(n+1\) 个点值进去,这样做一次卷积是 \(O(n)\) 的(只需要把点值对位相乘)。而我们最后只需要求 \(F_U\) 的系数,把得到的 \(F_U\) 的 \(n+1\) 个点值 \(O(n^2)\) 拉插回去即可。这样复杂度是 \(O(3^nn)\) 的。
4.22 A/CF2079B
考虑一个位置最后被加上的 \(x\) 肯定贡献是正的,然后从后往前符号是正负交替的。所以先把操作序列翻转一下,这样就变成把 \(x_i\) 划分为不超过 \(n\) 个子序列,每一个子序列里面的数正负交替求和。
考虑从前往后写下每个 \(x_i\) 的符号是啥,发现合法的充要条件就是前缀和时刻在 \([0,n]\) 内,因为前缀和表示的就是下一个需要减的子序列有几个。然后就有暴力 dp,设 \(dp_{i,j}\) 表示前 \(i\) 个位置前缀和为 \(j\) 的答案。
进一步,观察到 \(i\) 这一维有效的 \(j\) 必须奇偶性和 \(i\) 相同,打表后发现加上这个限制 dp 数组是凸的,每次转移可以看成和一个大小为 \(2\) 的数组做 \((\max,+)\) 卷积,multiset 维护差分即可 \(O(n\log n)\)。
4.22 B/P8326
首先猜测有解的充要条件是所有环长都是 \(8\) 的倍数。
考虑把一个环当成点,挡板当成边,每个挡板上下的两个环连边。先考虑如果颜色数是 \(2\) 怎么做,我们可以跑欧拉回路,根据边的方向来染色,因为欧拉回路里每个点出去的边和进入的边数量相等。
对于颜色数为 \(4\) 的情况,你注意只保留颜色数为 \(2\) 的某种颜色的边时每个点的度数仍然是 \(4\) 的倍数,那就分别再跑一遍染色就好了。
复杂度除排序外线性。
CF1616G Just Add an Edge
有点太厉害了。
先特判掉本身就存在哈密顿路径的情况。首先你观察一下加入一条边 \(x\to y,x>y\) 后产生哈密顿路径的条件是什么,发现就是你能把这个 DAG 划分成两条不交的链,第一条链的终点是 \(x\) 且第二条链的起点是 \(y\)。进一步地,假设这两条链分别是 \(l_1,l_2\),我们把 \(l_1\) 上的点颜色涂成 \(0\),\(l_2\) 上的点颜色涂成 \(1\),设 \(c_i\) 表示 \(i\) 的颜色,你会发现 \(\forall i\in[1,y),c_i=0\),同时 \(\forall i\in(x,n],c_i=1\)。
于是你得到了一个比较好看的条件。考虑枚举 \(y\) 是啥,设 \(dp_{i,w},w\in\{0,1\}\) 表示 \(c_{i-1}=w\) 且 \(c_i=w\oplus 1\) 是否可行,可以这么 dp 的原因在于 \(c\) 中一段连续的 \(0\) 或 \(1\) 你并不关心并且可以简单判断(你可以预处理出 \(nxt_i\) 表示 \(i\) 一直走 \(i+1\) 最远能走到哪),然后如果一个 \(x\) 满足 \(dp_{x,0}=1\) 且 \(nxt_x=n\) 那么 \((x,y)\) 就是一对答案,直接实现是 \(O(nm)\) 的。
考虑优化,你发现可能是 \(y\) 的位置只有一段前缀,可能是 \(x\) 的位置只有一段后缀,且这个前后缀不交(唯一的例外是只有一处走不到 \(i+1\) 的 \(i\),特判掉即可)。所以一个可能的思路就是,你找这么一个点使得两条路径在这里一定分开,然后就不需要枚举 \(y\) 跑很多次 dp 了。事实上这个点是有的,找到第一个走不到 \(i+1\) 的 \(i\),不妨记它为 \(p\),显然有 \(c_p\not= c_{p+1}\),所以我们从 \(p\) 开始向左右两侧分别 dp 拼起来就好了。具体来说,设 \(dp_{i,w}=\{0,1,2,3\}\) 表示 \(c_{i-1}=w\) 且 \(c_i=w\oplus 1\) 能不能通过 \(c_p=0,c_{p+1}=1\) 或 \(c_{p}=1,c_{p+1}=0\) 的状态走到,其中 \(0,1,2,3\) 三个值表示都不能,其中一个能,或者都可以。
复杂度又滑到了线性。
CF1863G Swaps
不太对味啊,感觉这怎么看都应该是一道 ARC 的题。
首先连边 \(i\to a_i\),得到一棵内向基环树,则一次操作相当于,把自己的出点改为原来指向点的出点,原来指向的点改成自环。
显然我们只需要计数能得到的有标号的不同基环树的形态即可,但是这也太难了。考虑换个角度看,就是你不要直接去考虑基环树的形态了,我们把操作 \(i\) 看成是把 \(i\to a_i\) 这条边打上标记,则现在一个点 \(i\) 实际指向的点就是不断走打过标记的边,直到找到第一个没打过标记的边,找到它指向的点。发现对标记的限制其实就是一个点的入边最多只有一条被打标记,所以对这个计数是容易的,每个点的贡献都是 \(\prod(in_u+1)\),乘起来即可。
别急这其实不对,考虑环上的情况,你发现一个环如果只剩下一条边没被打标记整个环也是自环了,而且我们不存在一种方案能使得整个环上的边都被打标记,所以单独考虑一个环的方案时我们应该再减去 \(\sum_{u\in cycle}in_u\),所以每个环的贡献要单独计算。
直接算就可以了,复杂度 \(O(n)\)。
P10789 [NOI2024] 登山
令 \(L_i=d_i-r_i\),\(R_i=d_i-l_i\),\(lim_i=d_i-h_i-1\)。
考虑暴力的 dp,设 \(dp_u\) 表示从 \(u\) 出发走到根的方案数,稍微分析下就可以发现我们要求下一步必须冲刺到 \(lim_u\) 或更浅的点上。
可以写出来暴力的转移:
也就是说先枚举我们滑落到了子树内的哪个点上,再枚举跳到了祖先上的哪个点。
容易发现,确定了滑落到子树内的哪个点后,能转移到 \(dp_u\) 的祖先 \(x\) 在深度上构成了区间,所以可以前缀和优化一下做到 \(O(n^2)\)。
这个 dp 的转移方式可以看作是,维护 \(coef_i\) 表示祖先 \(i\) 对 \(dp_u\) 的贡献系数,对于 \(u\) 子树内的每个点 \(v\),要对祖先上深度在某个区间内的点的 \(coef_i\) 加 \(1\),最后查询 \(\sum_{x\in anc(u)}coef_xdp_x\)。这个贡献的系数使用一些数据结构手段肯定是能够维护出来的,但是困难的地方在于维护出系数后我们需要知道所有祖先的 dp 值才能完成转移,而我们只能做到自下而上地维护出每个点祖先的系数。
那考虑使用一个支持撤销的数据结构来求出系数,这样算完自己的 dp 值之后撤销回去就能继续算子树内了。
这里考虑的方法是 dsu on tree,具体来说我们先做重链剖分,每个点开数据结构维护自己子树内每个点对应的贡献区间。为了方便,一个区间 \([l,r]\) 我们可以拆成 \([1,r]\) 贡献 \(1\) 和 \([1,l-1]\) 贡献 \(-1\),变成两次单点修改。对于一个点 \(u\),先继承重儿子的数据结构,轻儿子的可以启发式合并上来,然后要支持对数据结构内的所有点和 \(lim_u\) 做 check min,这个直接暴力做势能是正确的,具体实现可以使用 map。这样我们就能实现出来一个自下而上地维护系数的过程。
考虑计算完 \(u\) 自己的 dp 值之后如何撤销,其实也就是一个启发式分裂的过程。对于每个轻儿子 \(v\),我们可以暴力遍历它的子树来计算 \(f_v\),并撤销 \(v\) 子树内的点对当前系数的贡献,同时做完所有轻子树再撤销 \(u\) 这个点自己对系数的贡献。上面启发式合并的过程还有所有位置和 \(lim_u\) 取 \(\min\) 这一步,同样记录一下可以撤销。这样我们就以轻子树大小之和的复杂度撤销贡献得到了每个儿子的 dp 值,继续往下做下去即可。
最后复杂度是 \(O(n\log^2 n)\) 的,因为需要对 map 做启发式合并。写个 Splay 应该是可以做到 \(O(n\log n)\) 的。
CF1085G Beautiful Matrix
唐题,枚举完 LCP 之后,下面每行的贡献都是错排,后缀的贡献相当于计算 \(f_{i,j}\) 表示长度为 \(i\) 且有 \(j\) 个位置有限制时的错排数量。
随便做做就是 \(O(n^2\log n)\) 的了。
P10103 [GDKOI2023 提高组] 错排
莫队维护数学式子。
具体来说就是我们可以在知道 \(f_{i,j},f_{i,j+1}\) 的情况下支持 \(i\) 或 \(j\) 同时 \(+1\),然后可以回滚莫队。推导的过程其实没啥意思啊,懒得记了。
P9907 [COCI 2023/2024 #1] Mostovi
利用无向图 dfs 树的非树边都是返祖边的性质,转化为树上问题,虽然大部分是没啥水平的分类讨论。
显然是要在 dfs 树上考虑问题。先考虑删去一条非树边 \(u\to v\) 的情况,这里不妨假设 \(u\) 是 \(v\) 的祖先。删去 \(u,v\) 两个点后整棵树会被分为上,中,下三部分,上下两部分可能不存在但是问题不大。首先上部分自身必须是连通的,下部分会有若干棵子树,然后开始分讨:
- 下部分存在一棵子树没有向中上连边,则一定不连通,先判掉。
- 在此基础上,如果存在一棵下部分的子树向中上两部分都有连边,则仍然连通。
- 否则中部分应当有一条向上部分的连边。
考虑如何判,发现本质上其实都是只需要你求一棵子树向上伸出去的返祖边中最深的一条和最浅的一条,这个可以简单启发式合并/线段树合并维护出来,所以是好判的。当然第三种情况中间部分并不是一棵子树啊,但是它可以拆成 dfs 序上的 \(O(1)\) 段区间,这种情况我们只关心深度最浅的返祖边,再开一棵线段树在 dfs 序上维护区间最小值即可。
树边的情况也是类似的,直接分讨:
- \(u\) 是根节点:如果 \(u\) 在 dfs 树上的儿子数量 \(\ge 3\) 一定无解,如果 \(u\) 在 dfs 树上的儿子数量 \(=2\) 那么要求 \(v\) 在删去 \(u\) 之后是一个孤立点。如果 \(u\) 在 dfs 树上的儿子只有 \(v\),那么这要求 \(v\) 在 dfs 树上也只有一个儿子,或者 \(v\) 是孤立点。
- \(u\) 不是根节点:要求 u 的除了 \(v\) 的每个儿子都得有向上的连边,然后如果 \(v\) 有儿子就必须连到 \(u\) 的祖先上。
也是类似的嘛,直接做就好了。
复杂度如果实现使用启发式合并的话应该是 \(O(n\log^2 n)\) 的,使用线段树合并当然是 \(O(n\log n)\)。
P9732 [CEOI 2023] Trade
容易发现一个区间一定是卖掉前 \(k\) 大的波特,所以设 \(f(l,r)\) 表示区间内前 \(k\) 大的 \(s_i\) 之和,记 \(w(l,r)\) 表示一个区间的价值,则显然 \(w(l,r)=\sum_{i=l}^rc_i-f(l,r)\),使用前缀和和主席树可以做到 \(O(\log n)\) 计算一个 \(w(l,r)\)。
可以发现 \(w(l,r)\) 满足四边形不等式,也就是对于 \(a\le b\le c\le d\),都有 \(w(a,c)+w(b,d)\ge w(a,d)+w(b,c)\)。
所以说对于每个 \(r\) 来说随着 \(r\) 递增最优的 \(l\) 不降,可以决策单调性分治解决第一问,复杂度 \(O(n\log^2 n)\)。
考虑第二问,一个波特 \(i\) 可以被卖出当且仅当存在\(l\le i\le r\),使得 \(w(l,r)=ans\) 且 \(s_i\) 不小于区间第 \(k\) 大。如果暴力找出所有最优的区间去检查,这种区间最多显然有 \(O(n^2)\) 个,是不可取的,所以尝试寻找真正有用的区间满足什么性质。
如果区间之间没有包含关系那么性质是非常好的,因为这样区间随着 \(r\) 的递增 \(l\) 是不降的,就可以直接双指针做了。事实上确实是这样的,尝试说明这件事情。假如存在 \(l_1\le l_2\le r_2\le r_1\) 并且 \(w(l_1,r_1)=w(l_2,r_2)=ans\),那么根据上面的四边形不等式可以得到 \(w(l_1,r_2)+w(l_2,r_1)\ge w(l_1,r_1)+w(l_2,r_2)=2ans\),显然可以得到 \(w(l_1,r_2)=w(l_2,r_1)=ans\),此时你就可以发现 \([l_1,r_1]\) 这个区间是没用的了。
所以你可以在第一问分治的过程中对每个 \(r\) 求出最大的 \(i\) 使得 \(w(i,r)=ans\),记 \(p_r=i\)。然后第二问的时候直接双指针,右指针如果移动到了某个最优区间的右端点 \(r\),那么左指针也不断移动到 \(p_r\) 即可。这样你会得到 \(O(n)\) 个区间,每个区间要我们支持把区间内 \(\ge x\) 的数答案变为 \(1\),显然是好维护的。
所以最后的复杂度就是 \(O(n\log^2 n)\),瓶颈在第一问的分治。
QOJ5425 Proposition Composition
糖糖的,我喜欢。
首先如果你选两条额外边肯定不影响连通性,所以选的边肯定至少有一条是链边,然后分类讨论下几种情况:
- 如果选了一条没有被覆盖过的链边,另一条边怎么选都会不连通。
- 如果选了一条被覆盖过的链边,那么如果这条链边仅被一条额外边覆盖,再选择覆盖它的那条额外边就会不连通。
- 如果选了两条链边,我们发现当且仅当这两条边覆盖它们的额外边集合相同时会不连通。
然后考虑下这三类咋判。设 \(S_i\) 表示第 \(i\) 条链边覆盖它的额外边集合,前两种显然是容易的,你可以暴力维护 \(|S_i|\) 从 \(0\) 变 \(1\) 或者从 \(1\) 变 \(2\) 的过程。对于第三种,其实我们要求在每个 \(S_i\) 相同的等价类内选两个数的方案和。
考虑加入一条额外边对 \(S_i\) 的影响是啥,发现其实就是跨过覆盖区间两个端点的等价类会进行分裂,显然分裂次数总共只有 \(O(n)\) 次。这里可以考虑使用双向链表维护等价类,每次暴力找到所有 \(pre<l\) 和 \(nxt>r\) 的位置分裂它们所在的等价类。分裂后,我们需要更改原等价类的大小并求出新等价类的大小,这里可以使用启发式分裂,也就是分裂的时候给小的一部分暴力重标号即可(具体的实现你可以从分裂位置向左右两侧同时跳,先跳到边界的一侧就是较小的部分),因为这样你把分裂的过程倒着看其实就是启发式合并,复杂度 \(O(n\log n)\)。
4.25 A
考虑先求出总的路径数,每次询问减去删掉的路径数。具体怎么求呢?考虑一个暴力的思路就是直接容斥,先 \(O(2^k)\) 枚举钦定了哪些删掉的点必须被经过,计算这个的方案数可以把这些点按照拓扑序排序后简单计算。优化的方向很显然,你把所有删去的点按照拓扑序排序后使用带容斥系数的 dp 实现这个容斥过程即可,复杂度 \(O(n(n+m)+qk^2)\)。
4.25 B
先想想暴力怎么做,发现说你可以枚举一个叶子对表示你钦定这个路径最后是直径,然后你可以在这条链上 dp,设 \(f_{u,v,x}\) 表示当前操作了 \(x\) 次目前链上已经填好了 \(u\) 到 \(v\) 之间的边的权值,转移就是要么填一条链外边挂出来的边浪费掉,要么在链上向两侧扩展,这样得到了一个 \(O(n^5)\) 的做法。
考虑把它搬到树上去,发现同样的状态不能直接做的原因在于,你下一次扩展到的边可能被你之前某次乱用的操作填过了。一个简单的处理方式是,你可以直接记录你两个端点下一步要扩展到哪,这样乱用某次操作的决策可以规避掉上面的情况。具体来说,设 \(dp_{u,v,x,0/1,0/1}\) 表示当前两个端点在 \(u,v\),已经考虑了前 \(x\) 次操作,\(u/v\) 向链上的连边有没有被染过的答案,转移也是容易的,这样就是 \(O(n^3)\) 的了。
4.25 C
我草我真没想明白啊/yun。

浙公网安备 33010602011771号