2025.1.17 近期练习

CF1286D LCC

这个题还是比较简单的,考虑拆贡献,将所有碰撞情况拿出来考虑其出现的概率,显然只有相邻的。
按照时间排序。假设我们钦定了 \((i,i+1)\) 这对碰撞为最先碰撞的,那么需要满足若干条件:
例如若 \(j\) 向右,\(j+1\) 不能向左等,因为限制只存在于相邻两位,我们可以考虑 dp 过去。
然后这就是一个动态 dp 了,不需要让 \(i\) 向左向右都 dp 一遍,其实只需要从 \(1\) 扫到 \(n\) 即可。
矩阵+线段树维护即可。

P9068 [Ynoi Easy Round 2022] 超人机械 TEST_95

cdq 分治。考虑拆贡献到每个点对 \((x,y)\),那么其能贡献的条件是 \(fir_x<las_y\)\(x>y\)
对于单点修改每次只会产生 \(O(1)\) 次对 \(fir,las\) 的改变。考虑用 set 维护即可。
考虑将修改拆到 \(las,fir\) 两部分,这两部分互不重复。
如果要 cdq 分治我们需要将其转化为偏序形式,我们将所有修改都记为 \((x,y,t,0/1,-1/1)\)
表示 \(fir_x/las_x\) 改为 \(y\),此时在 \(t\) 时刻,是要加上还是减掉。
那么考虑 \((x_0,y_0,t_0,0)\)\((x_1,y_1,t_1,1)\) 之间的贡献条件是 \(x_0<x_1,y_0>y_1\)\(t_0,t_1\) 谁大就贡献到哪。
然后得到每个时刻的变化量,最后只需要将前缀加起来即可。所以对时间这维做 cdq 即可。
注意修改是不变的情况。合并两个归并的区间可以用 inplace_merge 函数。

P5163 WD与地图

折磨。首先删边转化为加边,相当于将时间轴倒序。
我们如果能知道一条边两端点被并到一个 scc 里的时间,我们就能用线段树合并轻松解决。
观察特殊性质,对于单条边,答案满足可二分性,对于多条边的情况,我们可以尝试整体二分。
对于当前二分的时间区间 \([l,r]\),对于时间在 \([0,mid]\) 的所有边都加入,然后用 Tarjan 来缩点。
如果某条边已被缩起来,那么其之后也一定被缩,往左区间递归;反之右区间递归。
当然我们每次都取出 \([0,mid]\) 的边是复杂度错误的,考虑利用上一层的缩点信息。
即我们在 \([l,r]\) 已经用了 \([0,mid]\) 的边,直接去右儿子 \([mid+1,r]\) 可以直接用上。
然后考虑用可撤销并查集维护缩点,去左儿子 \([l,mid]\) 的时候撤销当前区间的操作即可。
我们可以只加入 \(r-l+1\) 条边,即将并查集维护的 SCC 连起来。
然后我们把新连边的点拿出来跑 Tarjan,这样复杂度跟区间大小挂上。
但是原来 SCC 之间连的边怎么办?我们可以忽略。如果新边和 SCC 之间边可以构成新的 SCC,
那么边集中的该条边一定会在之前的二分中被划分到答案更早的区间,所以不会出现在当前区间里。
于是每条边最多经过 \(O(\log m)\) 层,并查集算 \(O(\log n)\),复杂度 \(O(n\log ^2n)\)。线段树合并 \(O(n\log n)\)

P5069 [Ynoi2015] 纵使日薄西山

观察到,如果操作了 \(a_x\) 那么 \(a_{x-1},a_{x+1}\) 肯定都不会被操作,因为他们之间相互大小不会变。
那么这个 \(a_x\) 一定是一直被操作直到 \(0\) 的,此时 \(a_{x-1},a_{x+1}\) 也肯定为 \(0\)
其次,我们不必模拟整个过程,我们只需要直到哪些 \(a_x\) 被操作了即可,维护 \(a_x\) 集合。
考虑模型化。我们可以把问题转化为 \(01\) 序列,\(0/1\) 表示没选或者选了。然后观察这个序列的性质。
首先 \(0,n+1\) 两个位置取 \(0\)\(0\) 的连续段不超过 \(2\);每个 \(0\) 至少有一个相邻的 \(1\) 比它大。
那么这是一个动态 dp 的形式,我们直接从 \(1\) 扫到 \(n\) 即可。
三个状态:取 \(1\);取 \(0\) 且满足有相邻 \(1\) 比它大;取 \(0\) 但没有相邻 \(1\) 比它大。矩阵处理。
因为我们整个过程是确定的,所以一定只有一种合法的 \(01\) 序列。至于有多种转移我们可以先存着。
矩乘写 \((\max,+)\) 矩乘,用 \(-inf\) 表示没有转移。

P4117 [Ynoi2018] 五彩斑斓的世界

第二分块。考虑弱化题目,如果是整体操作查询该怎么做。发现每次操作势能都减少。
设最大值为 \(mx\)。若 \(2x> mx\),那么考虑将 \([x+1,mx]\) 区间整体平移;
\(2x\le mx\),那么考虑将 \([1,x]\) 平移;同时打上全局减的 tag。
那么我们这样操作只需要复杂度 \(O(V)\),而 \(V\le 10^5\) 刚好契合我们的做法。
考虑平移操作该如何实现,我们可以维护相同值的数位置的集合,用并查集维护。
具体是并查集维护位置的集合关系,合并就是把值所在集合的根连边过去,注意判断连过去有没有根。
那么回到原题我们可以考虑将序列分块,如果是整块的操作那么显然就是整体操作;
如果是散块的操作,我们考虑直接重构整个块。
本题卡空间,而每个块是独立的,所以我们离线每个块都做一遍即可。

CF1129D Isolation

这个题属于典题。\(dp_i=\sum _{calc(j+1,i)\le k}dp_{j}\),其中 \(calc\) 表示出现次数为 \(1\) 的元素数量。
我们考虑加入 \(i\) 会对所有 \(calc(j+1,i)\) 造成什么影响,设 \(pre_i\) 表示上一次出现的位置。
那么 \(j\in[pre_{pre_i},pre_i)\)\(j\) 会减去 \(1\)\(j\in [pre_i,i-1]\)\(1\),转化为区间操作,查询值 \(\le k\) 的权值和。
这个显然非 poly,考虑分块,由于是加减 \(1\) 形式操作,所以我们只需要算原来 \(k-1,k+1\) 的贡献。
我们维护块里每个元素可以考虑维护一个桶表示当前值为 \(x\) 的和 \(s_x\)
整块修改直接打 \(tag\);散块简单修改。

P6578 [Ynoi2019] 魔法少女网站

考虑没有修改弱化版。考虑将所有数从小到大加入,设已加入为 \(1\),那么维护连续的 \(1\) 构成的段即可。
其次将所有询问离线,只需要在对应时间上查询答案即可。线段树维护。
考虑加上修改,那么相当于修改若干的 \(0/1\)。我们容易想到操作分块。
考虑操作分块后块内如何做,还是将所有数从小到大加入,对于每个询问,将其对应的改了即可。
但是我们别用线段树,因为查询是 \(O(n)\),但修改是 \(O(n\sqrt n)\),考虑给序列分块。需要支持撤销。
这里省略细节。总复杂度平衡后是 \(O(n\sqrt n)\)
不需要用到并查集,只需要维护每个值所在位置区间最左和最右。
注意规避 \(1\to 0\) 的改变,这是很难处理的,只能处理 \(0\to 1\) 的操作。

P6466 分散层叠算法(Fractional Cascading)

这是个模板题。考虑对 \(k\) 个序列建线段树,每个节点维护一个有序数组。
对于叶子结点,第 \(i\) 个叶子结点的数组即为给出的序列 \(i\)
对于非叶子结点,我们将其左右儿子的序列从大到小,每隔 \(p\) 个取出一个数,得到数组 \(L,R\)
再将 \(L,R\) 归并(从小到大),得到该节点的数组,同时记录每个位置是从哪来的,设为红色/蓝色。
对于一次询问,我们在根节点数组上二分出 \(x\) 的后继 \(t\),考虑如何得到左儿子和右儿子中 \(x\) 的后继。
\(t\) 为红色,该位置为 \(i\),那么在左儿子里后继的位置一定是 \((i-p,i]\) 之间;右儿子同理。
我们需要找到 \(i\) 后面第一个异色的位置 \(j\),这个可以在归并时顺带实现,那么右儿子在 \((j-p,j]\) 之间。
知道了左右儿子分别后继位置区间,直接暴力判断出实际位置,继续递归下去即可。
注意到单次查询每个节点贡献都是常数,所以是 \(O(\log n+k)\) 的查询复杂度。
线段树方法的优势在于可以支持修改和区间查询,更方便维护一些分块中多块二分的问题。

P5356 [Ynoi2017] 由乃打扑克

这个题显然分块,然后瓶颈在于多块同时二分的问题,这个问题考虑分散层叠算法,线段树实现。
因为问题带修,所以我们考虑取 \(K=3\),修改合并复杂度是 \(O(B+\frac{2}{3}B+\frac{4}{9}B+\dots)=O(B)\)
区间修改打 tag,复杂度仍然 \(O(B)\)。散块修改考虑归并,复杂度相同,所以修改是 \(O(B)\) 的。
考虑查询,按照上述方法直接遍历整颗线段树即可,复杂度 \(O(n/B+\log n)\),可以忽略 \(+\log n\)
考虑散块查询可以直接拎出来 \(O(B)\),查询时直接二分,这个可以忽略了。
查询加上二分,那么查询是 \(O(n/B\log n)\),平衡一下 \(O(B+n/B\log n)\),取 \(B=\sqrt{n\log n}\) 即可。
总的复杂度即 \(O(n\sqrt{n\log n})\),复杂度极优,但常数略大。

P7811 [JRKSJ R2] 你的名字。

注意到 \(k,a_i\le 10^5\),考虑根号分治。设阈值为 \(B\)
对于 \(k\le B\) 的考虑暴力,将所有 \(k\) 相同的拿出来一起做。我们只需要 \(O(n)\) 预处理的 RMQ 即可完成。
事实上线段树或分块都是 \(O(n)\) 预处理的,这里复杂度是 \(O(Bn+m\log n)\)
对于 \(k>B\) 的转化问题,枚举 \(c\),相当于计算所有满足 \(a_i\ge ck\),此时 \(a_i-ck\) 的最小值。
\(c\le n/B\),所以此时相当于有 \(nm/B\) 个问题。注意空间问题。考虑不把 \(ck\) 求出来,枚举因数即可。
离线,不妨将 \(a_i\) 从大到小插入,询问从到到小排,然后双指针维护询问值,相当于查询区间最小值。
我们需要将询问做到 \(O(1)\),不妨借鉴猫树。但是猫树修改是 \(O(n)\) 的,考虑分块,猫树维护所有块。
每个块内分别记录前缀最小值和后缀最小值即可。注意在一个块内的询问直接暴力。
其实维护所有块 RMQ 可以直接 ST 表处理,单点修只会造成 \(1+2+4+\dots+\sqrt n=O(\sqrt n)\) 次改变。
\(B=\sqrt {10^5}\) 达到平衡。

P4119 [Ynoi2018] 未来日记

第一分块。跟第二分块还是很像的,都是块内维护并查集。
如果我们用二分,那么不可避免多块同时二分问题,如果采用分散层叠的话还不能支持区间打 tag。
看到值域只有 \(10^5\) 考虑在值域上也进行分块,维护 \(cnt_{i,j}\) 表示前 \(i\) 个序列的块中有多少个 \(j\)
\(sum_{i,j}\) 表示前 \(i\) 个序列的块中有多少个值域第 \(j\) 个块的数。
整块修改只触及两个值域的位置,散块直接重构即可。所以修改是 \(O(\sqrt n)\) 的。
考虑查询操作就是先取出散块然后 \(O(\sqrt n)\)\(sum,cnt\) 上面跳即可。
复杂度 \(O(n\sqrt n)\),取 \(B=500\) 极限 998ms 通过。

P4192 旅行规划

转化为区间加等差数列,区间 \(\max\)。考虑分块。一般分块完我们就思考块内整体操作我们如何实现。
由于等差数列+等差数列还是等差数列,我们只需要维护(块内)全局标记 \(k,b\),表示 \(a'_i=a_i+ki+b\)
考虑如何快速求全局 \(\max\)。考虑 \(a'_i>a'_j\) 的条件,转化后得到 \(\frac{a_i-a_j}{i-j}> -k\)
跟斜率相关的式子我们可以转化为凸包。这是一个上凸包的形式,我们只需要在凸包上二分即可。
于是每个块维护凸包,整块只需要 +tag,散块直接重构,复杂度 \(O(n(\frac{n}{B} \log n+B))\ge O(n\sqrt {n\log n})\)

P9962 [THUPC 2024 初赛] 一棵树

由于贡献是在边上算的,我们自然想到以子树划分子任务进行树形 dp。
\(f_{u,i}\) 表示 \(u\) 子树内选了 \(i\) 个黑点此时最小代价,那么考虑合并子树 \(v\)
得到 \(f_{u,i}=\max_{j+k=i}f_{u,j}+f_{v,k}+|2x-k|\),这是一个 \((\min,+)\) 卷积的形式。
优化 \((\min,+)\) 卷积的关键是判断函数的凸性。\(|2x-k|\) 是下凸,而 \(f_{v}\)\(v\) 的儿子转移而来,也是凸的。
那么我们就可以考虑用闵可夫斯基和来 \((\min,+)\) 卷积,具体是归并差分数组,可以用贪心思想证明。
考虑用数据结构维护差分数组,比如平衡树,用启发式合并维护。
考虑处理加上函数 \(|2x-k|\),这个可以转化为差分后前缀 \(-2\),后缀 \(+2\),平衡树简单维护。
复杂度 \(O(n\log^2n)\)

P5986 [PA2019] Szprotki i szczupaki

贪心显然是每次选最大能吃的吃。当前最小不能吃的鱼只会变化 \(O(\log V)\) 次,因为每次值都翻倍。
那么我们考虑依此给贪心分段。
具体地,设当前最小不能吃的鱼为 \(t\),我们就从能吃的鱼中二分一个后缀吃掉,直到能吃 \(t\)
注意此时可能不止能吃 \(t\),挑最大能吃的吃了即可。用平衡树维护,复杂度 \(O(q\log n\log V)\)

CF809D Hitchhiking in the Baltic States

如果按照正常的 dp 设 \(f_{i}\) 表示 \(i\) 结尾的最长子序列,这个很难优化,考虑更换状态设计。
不妨设 \(f_i\) 表示长度为 \(i\) 的上升子序列最后一位最小是多少。
若当前区间为 \([l,r]\),那么若 \(f_i \in [l-1,r-1]\),那么 \(f_{i+1}=\min (f_{i+1},f_i+1)\)
\(f_i< l-1\),那么 \(f_{i+1}=\min(f_{i+1},l)\)
注意到 \(f\) 显然具有严格单调性,所以上述取 \(\min\) 的操作都不需要,相当于直接赋值。
考虑二分出 \(f_{i}\in[l-1,r-1]\) 的区间 \(i\in [x,y]\)
转移相当于 \([x,y]\) 平移 \(+1\) 个位置,然后整体加一。然后令 \(f_x=l\)
具体操作用平衡树实现。最后答案即为平衡树的大小。

CF1416E Split

属于是数据结构优化暴力。设 \(dp_{i,j}\) 表示填了 \(b_{1\sim 2i}\)\(b_{2i}=j\) 的最小代价。
\(dp_{i,j}=\min(dp_{i-1,a_i-j}+1,dp_{i-1,k}+2)-[j=\frac{1}{2}a_i]\)
感觉这个 \(j,a_i-j\) 的下标很像区间翻转,那么直接上平衡树即可。
只需要支持单点修改,全局推平,全局加,全局翻转,求全局 \(\min\),后缀删除,后缀插入即可。
\(dp_{i,j}\) 减去 \(i\) 即可去掉全局加。 \(dp_{i,j}=\min(dp_{i-1,a_i-j},dp_{i-1,k}+1)-[j=\frac{1}{2}a_i]\)
\(a_i\) 比较大的话,平衡树改为维护若干个区间,区间内的值相同,修改就分裂区间即可。

P5494 【模板】线段树分裂

关于 FHQ-treap 合并两个值域相交的部分,复杂度势能分析解决。
一次分裂会减少 \(\log⁡V\) 的势能。一次合并如果增加就至多增加 \(\log⁡V\) 的势能。
具体地,假设合并 \(x,y\),钦定 \(pri_x>pri_y\),那么以 \(val_x\) 为权值分裂 \(y\),递归合并下去。
复杂度是 \(O(n\log n\log V)\) 的,但是常数小。
这个可以拓展到区间取模,也是可以势能分析且复杂度正确。
势能分析待学习。

CF1455G Forbidden Value

不难有一个简单的 dp 设 \(f_{i,j}\) 表示执行了前 \(i\) 句语句,当前值是 \(v\) 的最小代价。
但是转移很难受,因为我们要跳过一个 if 从以前的版本转移,所以考虑建树。
然后做树形 dp,线段树合并处理。这个玩意叫“整体 dp”?

CF671D Roads in Yusland

树上问题考虑以子树为子任务划分。设 \(f_{u,j}\) 表示 \(u\) 子树已经覆盖,同时向上伸出到深度 \(j\)
那么考虑合并 \(u\) 的儿子 \(v\),那么 \(f_{u,j}=\min(f_{u,j}+(\min_{k\ge j}f_{v,j}),f_{v,j}+(\min_{k\ge j}f_{u,j}))\)
考虑线段树合并处理。考虑在合并的时候记一下后缀 \(\min\) 即可。注意先递归右儿子再递归左儿子。
考虑把路径挂在深度较深的点上,设存在 \(anc\to u\) 的路径,权值为 \(w\)
合并完 \(u\) 的儿子 \(v\),我们就用 \(\min_{k\ge d_{anc}}+w\) 更新 \(j=d_{anc}\) 的位置。
所以我们线段树除了合并操作只需要实现区间最小值,单点 chkmin,区间加即可。
为什么复杂度是对的?考虑势能分析,注意势能不是 \(\sum dep_u\) 而是路径的个数,即状态数是 \(O(n)\)
这也是被称为整体 dp,还是比较地基础的。
写垃圾回收节省空间。

CF671E Organizing a Race

考虑什么条件下 \(l,r\) 之间可以往返。考虑对 \(w,g\) 的前缀和 \(a,b\),令 \(c_i = a_{i-1} - b_{i-1},d_i = b_i - a_{i-1}\)
那么条件是 \(c_l=\min_{i\in[l,r]}c_i,d_r=\max_{i\in[l,r]}d_i\)
考虑我们操作 \(g_i\)\(1\) 的影响,则对于 \(j \ge i\)\(b_j\)\(1\);对于 \(j > i\)\(c_j\)\(1\);对于 \(j \ge i\)\(d_j\)\(1\)
先考虑 \(l \to r\)。从 \(n\)\(1\) 枚举 \(l\),用单调栈维护一个单调上升的子序列 \(c_{l} < c_{x_1} < c_{x_2} < \cdots\)
贪心地依次对 \(g_{x_1-1},g_{x_2-1},\cdots\)\(1\),每次将 \(g_{x_i-1}\)\(c_{x_i} - c_{x_{i-1}}\)。这么做可以对满足 \(d\) 的条件最优。
再考虑 \(r\to l\),现在我们只需要再 \(d_r\) 处加剩下的 \(k\) 即可。
由于之前每次操作一定会操作到 \(d_r\),则条件变为 \(d_r+k\ge \max_{i\in [l,r-1]} d'_i\)
考虑先在单调栈上二分出一个区间 \([l,r']\) 表示满足 \(c\) 的条件,\(r\) 一定会在这个范围中。
考虑满足 \(d\) 的条件,可以用线段树二分解决,即若 \(\max d_{rs}+k\ge \max_{i\in 1,mid}d'_i\) 就往右儿子递归。
在二分过程中顺便维护前缀最大值。所以线段树维护 \(d,d'\) 的最大值即可。

Loj#6029. 「雅礼集训 2017 Day1」市场

首先观察到 \(f(x)=x-x/d\) 是单调不降的,代表减去的值。
关于区间整除,做法是暴力递归所有区间直到 \(l=r\) 或者 \(f(\max_p)=f(\min_p)\) 打上区间减标记。
考虑势能分析。设势能为 \(\sum \log |a_i-a_{i-1}|\)
对于除法操作,每个连续段满足 \(f(\max_p)=f(\min_p)\) 在线段树上分成 \(\log\) 段,而相邻连续段之间势能至少减少 \(1\),相当于用 \(1\) 的势能摊掉了 \(\log\) 的代价。
对于加操作,内部势能不变,两边增加了 \(\log\)
所以总的势能是 \(O(n\log n)\),算法复杂度是 \(O(n\log ^2n)\),没有区分 \(\log n,\log V\)
卡老师讲考虑容量为极差。若极差为 \(x\) 那么每个区间最多被操作 \(O(\log x)\) 次。

Loj#6507. 「雅礼集训 2018 Day7」A

考虑区间容量为“同时含有 \(0/1\) 的二进制位数”。维护区间或和 \(o\),与和 \(a\),同时维护最小值。
考虑 \(\&k\) 操作。考虑暴力递归到叶子,若 \(k\subseteq a\) 那么对该区间无效,或者 \(a\&k=o\&k\) 可直接打 tag。
具体地,若 \(a\&k=o\&k\) 那么 \(\& k\) 操作的位置只会是都为 \(0\) 或都为 \(1\),可以直接操作;反之容量减少 \(1\)
\(|k\) 操作同理。那么每个区间只会被操作 \(\log V\) 次,对应容量,复杂度就是两只 \(\log\)
注意 tag 的先后顺序。
这个势能可以设计为 \(\sum popcount(a_i\otimes a_{i+1})\)

CF793F Julia the snail

考虑利用 \(r_i\) 互不相同的条件,那么我们考虑扫描线扫 \(y\) 这一维,维护所有 \(x\) 的答案。
考虑从小到大扫,那么若当前扫到 \(y\),若存在 \(r_i=y\),我们要将区间在 \([1,l_i]\) 且答案 \(\ge l_i\) 的设为 \(r_i\)
考虑直接套用 Segbeats 的结论。每次操作若不是操作最大值,至少将最大值和次大值合并,势能减少。
而总势能是 \(O(n\log n)\) 的。每次用 \(\log n\) 代价减少 \(\log n\) 势能,复杂度为 \(O(n\log n)\)
还是维护最大值,次大值即可。修改最大值的时候最大值是谁是不会变的方便维护。

P5609 [Ynoi2013] 对数据结构的爱

这是一个线段树维护分段函数的问题,注意到函数的断点个数显然不超过区间长度。
考虑维护 \(c_x\) 表示对于区间 \(p\) 而言最小的输入值使得减去 \(x\)\(p\),而这么维护状态数是 \(O(n\log n)\)
考虑 pushup。若 \(c_{ls,x+1}-1+sum_{ls}-xp\ge c_y\),那么可以用 \(\max(c_x,c_y+xp-sum_{ls})\) 更新 \(c_{x+y}\)
合并是 \(O(n^2)\) 的,观察题解得到:没什么其他路可走,就只能考虑有没有单调性来加快计算的速度。
观察到:\(c_x-c_{x-1}\ge p\)。那么若 \(f(x,y)=\max(c_x,c_y+xp-sum_{ls})\),则 \(f(x,y)<f(x+1,y-1)\)
所以关于更新 \(c_{x+y}\)\(y\) 最大且合法时就取到答案,这个考虑双指针处理即可。所以合并是线性的。
询问的话直接拆成 \(O(\log n)\) 个区间,每个区间二分即可。
一个卡常技巧,“查理线段树”,令左儿子为 \(2mid\),右儿子为 \(2mid+1\),减少指针跳跃。

uoj#637. 【美团杯2021】A. 数据结构

考虑更换贡献方向,考虑询问离线,用每个出现 \(x\) 来贡献询问。
进一步地,考虑正难则反,计算不出现的 \(x\),考虑一个 \(x\) 不出现的条件。
有两个条件:\(x\) 在区间 \([l,r]\) 里,且 \(x-1\) 不在区间 \([l,r]\) 里。
相当于 \([l,r]\) 包括 \([fir_x,ed_x]\) ;取出 \(x-1\) 出现相邻的两个位置 \(p1,p2\),那么 \([l,r]\) 被任一 \((p1,p2)\) 包括。
\([l,r]\) 看成平面上的点。那么相当于 \((l,r)\in [1,fir_x]\times [ed_x,n]\)\((l,r)\in (p_1,p_2)\times (p_1,p_2)\)
将两矩形求交,那么 \((l,r)\) 被多少个区间包括就被贡献多少次,这是个扫描线模型。

posted @ 2025-01-18 22:15  s1monG  阅读(42)  评论(0)    收藏  举报