数据结构杂题-nyh
记忆
首先 Trie 树整体 \(\pm 1\) 是容易的,只需要从低位插入,然后一直交换左右儿子然后往左/右递归即可。整体异或也是容易的,只需要打 tag,处理时判断自己这一位需不需要翻转左右儿子即可。
查一个数在插入后若干次操作后的值,记录这个数插入后的点编号,从这个点开始不断访问父亲即可知道自己的值。这里记得先跳到根 pushdown
。
先考虑向上走的部分,在 \(u\) 这个节点插入,在 \(lca\) 位置取出,为避免可持久化,进入某个儿子时先整体减,整体异或,出来时整体加。
回溯时做插入,然后整体异或,接着做查询。
再考虑向下走的部分。在 lca 在 \(v\) 方向的儿子处加入,在 \(v\) 处查询。
先加入接着整体异或,然后查询,最后进入子树。
进入子树时整体加,退出子树时整体减。回溯时整体异或。
对树递归处理向上向下路径的方法
JOISC2021 J 聚会
做一点分析。显然合法重心是一条链。当 \(i\) 为奇数时显然答案为 \(1\)。
同时设这条链是 \((u,v)\),设 \(s_1\) 为 \(u\) 远离 \(v\) 方向的子树大小,\(s_2\) 为 \(v\) 远离 \(u\) 方向的子树大小。
会对 \(2,4,\dots 2\min(s_1,s_2)\) 的答案对 \(dis(u,v)\) 取 \(\max\)。
首先点分治做法是容易的。只需要维护以 \(s\) 为下标 \(d\) 为权值的后缀和即可。
然后考虑一些更需要脑子的做法。
- 当 \(u,v\) 没有祖孙关系,显然就是 \(\min(sz_u,sz_v)\)
- 当 \(u\) 是 \(v\) 祖先,显然就是 \(\min(n-sz_u+1,sz_v)\)
考虑以树重心为根,特别处理树重心,则 \(n-sz_u+1\ge \frac{n}{2}\ge sz_v\),因此 \(\min(n-sz_u+1,sz_v)\) 等效为 \(\min(sz_u,sz_v)\)。
所以所有的限制都是 \(\min(sz_u,sz_v)\)。
因此按 \(sz\) 从大到小加入点,问题变为动态维护树直径长度。
利用重心为树根,解决关于不同方向 \(sz\) 的限制
The closest pair
注意到所有数字不相同,这启发我们调和级数。
枚举 \(a_i\),枚举 \([ta_i,ta_i+a_i-1]\) 的范围,对这个范围内的数字算支配区间。
设有两个数 \(x,y \in [ta_i,ta_i+a_i-1],pos_x< pos_y<i\),则 \(x\) 有用,至少 \(w(x,a_i)<\min(w(y,a_i),w(x,y))\)。
\(w(x,a_i)<w(y,a_i)\implies x<y\)。
\(w(x,a_i)<w(x,y)\implies x\bmod a_i<y\bmod x<\frac{y}{2}\implies x<\frac{y+ta_i}{2}\)。
所以可以看到,取值区间 \([ta_i,r]\),每次找到一个值 \(y\),都会变成 \([ta_i,\frac{(y+ta_i)}{2}]\) 也就是缩小一半,所以最多选出 $\log $ 个元素。
放宽限制,每次找到在取值区间范围的小于 \(i\) 的最大位置 \(x\),将其加入决策集合即可。
这一步可以使用线段树,以值为下标,原数组下标为值,查区间最小值即可。
右边是同理的。最后询问就做一个扫描线即可。
\(O(n\log ^3n)\),但远远跑不满。
有效模运算至少减少两倍,倒转权值与下标,查找某个范围内最接近某个点的位置
P3687 仙人掌
判断初始状态是否合法是容易的。合法后断掉不能再被覆盖的边,变成了在一棵树上,加入若干非树边,使得每条边最多位于一个环上的方案数。不能加入重边。
不妨设 \(dp_{i,0/1}\) 表示子树 \(i\) 合并后,能否向上连的方案数。
这是为了避免重边。
转移:转移到 \(dp_{i,0}\) 的情况,就是
- 有些子树不连出去,就连在 \(i\) 上,或者不连,方案数 \((dp_{j,0}+dp_{j,1})\)
- 有些子树要连出去,这时候不需要顾忌重边,可以从 \(j\) 开始,还是 \((dp_{j,0}+dp_{j,1})\)
转移到 \(dp_{i,1}\) 的情况是类似的,不过两者分别要求第二类子树有偶数/奇数个,同时将这些方案数乘起来后需要一个分配系数。
设有 \(c\) 个儿子,\(dp_{i,0}\) 需要乘上 $\sum_{i=0}^{2i\le c}{n\choose 2i}(2i-1)(2i-3)\dots $,如果是 \(dp_{i,1}\) 需要乘上 $\sum_{i=0}^{2i+1\le c}{n\choose 2i+1}(2i+1)(2i-1)\dots $。
最后把每个连通块方案乘起来即可。
别忘记分配配对方案。
P8923 Many Minimizations
考虑原问题。
显然有 dp:\(f_{i,j}=(\min_{k\le j}f_{i-1,k})+|j-a_i|\)。
显然,加下凸函数,前缀 \(\min\),有凸性,考虑使用 slope trick 优化。
开一个大根堆,每次执行如下操作:
- 加入两个斜率转折点 \(a_i\)
- 将斜率为正的转折点删掉
考虑到,每次会使得最大斜率增加 \(1\),则意味着每次只会删最大的转折点,那么操作可以描述为:
开一个大根堆,每次先加入两个 \(a_i\),然后弹出堆顶。
答案嘛,自然 \(b_i\) 就是每次弹出的堆顶排序后的结果了。(这个位置在斜率为零的段的最右侧,取了准没错)。
也就是设 \(max_i\) 为每次弹出的值,答案表达为 \(\sum max_i-a_i\)。
如何求这个值呢?
所有情况的 \(\sum a_i\) 和是容易的,显然是 \(n·m^{n-1}·\frac{m(m+1)}{2}\)。
那么 \(\sum max_i\) 怎么算,考虑转化为 01 序列的问题,枚举一个 \(v\) 从 \(1\sim m\),求出所有情况下有多少个 \(max_i\) 不小于 \(v\)。
可以看做每次加入两个 \(0/1\),然后每次删掉最大值(有 \(1\) 就删 \(1\),没 \(1\) 就删 \(0\))
可以设 \(dp_{i,j,k}\) 表示,填完了 \(a_{1\sim i}\),当前还有 \(j\) 个 \(1\),\(a\) 中有几个数是 \(1\)。
有两类转移:
- \(dp_{i,j,k}\to dp_{i,\max(0,j-1),k}\)
- \(dp_{i,j,k}\to dp_{i,j+1,k+1}\)
最终 \(\sum max_i-a_i\) 总和就是
\(dp_{n,j,k}\) 怎么求?
观察一下可以发现,首先 \(j\) 的总增量是 \(k\),则总减量为 \(k-j\)。剩下的 \(n-(k+(k-j))\) 个位置哪里去了呢?在这些位置时 \(j=0\)。
也就是说,我们要求的是一条从 \((0,0)\to(n,j)\),每一步位移有 \((1,1),(1,-1)\) 两种选择,当在 \(x\) 轴上的时候第二种选择变成 \((1,0)\)。使用了 \(k\) 个 \((1,1)\),\(k-j\) 个 \((1,-1)\),\(n-2k+j\) 个 \((1,0)\) 的路径条数。
由对称法,这个可以等效为 \((0,0)\to (n,j-(n-2k+j))\),路径最低点 \(y\) 坐标为 \(-(n-2k+j)\) 的方案数。
使用反射容斥可以得出最低点坐标高于 \(-n+2k-j\) 的方案数是 \({n\choose k+j}-{n\choose k-j}\)
恰好经过的就是 \({n\choose k-j}-{n\choose k-j-1}\)。
这里注意了,这里需要满足 \(n-2k+j\ge 0\),否则可能出现路径本身非法但组合数算出来有值的情况。
那么可以算出一个 \(g_k\) 表示 \(\sum_{j=0}^ndp_{n,j,k}(k-j)\)。
答案可以写为:
将 \(v\) 看做变量,二项式展开,化简后可以得到一个 \(n\) 次多项式,带入每个 \(v=0,2,3\dots m\) 计算答案,由于系数已知,相当于算自然数等幂和,平方求解即可。
\(O(n^2)\)。
P5609 对数据结构的爱
这很神奇了。考虑使用线段树维护。
显然有一个性质是初始值越大,则其减掉 \(p\) 的次数不会变少。
设 \(f_{x,l,r,i}\) 为线段树节点 \(x\) 所负责的区间 \([l,r]\),减掉 \(i\) 次 \(p\) 的最小初始值。
初始化 \(f_{leave,i,i,0}=-\infty,f_{leave,i,i,1}=p-a_i\)。
考虑左右子树做卷积合并。已知 \(f\) 后这个问题是容易的(线段树,先递归左边再递归右边,访问到完全包含的一段时直接做 upper_bound,并结合区间和就可以知道 result 变成了多少)。
注意到 \(f_{x,i}+p\ge f_{x,i+1}\implies w(i,j)\le w(i+1,j-1)\)。
因此可以使用双指针,初始化让 \(i=j=0\),每次优先移动 \(j\),移动不了后移动 \(i\) 即可。
由这个性质推知双指针解法,很关键。
QOJ6119
首先我们需要知道如何去计算最小次数。将 (
视作 \(1\),将 )
视作 \(-1\)。
显然在开头添加 )
和在结尾添加 (
的个数是确定的,根据和推知。
接着对于一个左右括号个数相同的串,又该如何求呢?
Deepseek 告诉我们,做前缀和后,对于每个
-1
的位置,若其对应的前缀和是负数,答案加上其绝对值。考虑证明:
一个显而易见的贪心结论是:用栈扫一遍,若当前是左括号,入栈,否则检查栈是否为空,若为空,将后面最近的左括号提到这个右括号前面。
如何计算次数呢?考虑计算每个右括号被多少个左括号前移跨过即可。
这个次数显然就是该位置若前缀和为负数,则为其绝对值,否则为零次。
问题转化为:给定一个由 \((1,-1)\) 构成的序列,支持以下操作:
- 区间乘 \(-1\)。
- 取出区间 \([l,r]\),求前缀和,拿出每个 \(-1\) 位置对应前缀和 \(s_i\),将 \(\max(0,-s_i)\) 求和。
这样的一个操作,放线段树上感觉很有难度,感觉不弱于区间 \(\ge k\) 的数字求和的问题。
因此使用对数数据结构至少是个两个,考虑分块。
区间乘 \(-1\) 的操作可以提前预处理一遍乘 \(-1\) 后的结果,这样只需要打个 tag
表示现在真实情况哪个版本。
考虑你怎么算答案。
本质上是一个给定 \(k\),求所有 \(\le k\) 的数字和的问题。(这里如果原位置是左括号不参与统计答案)。
分块维护此类问题一般用桶算了之后求个前缀和完事,因为有效值范围只有 \([-block,block]\),\(block\) 是块长。
这样就做完了,修改和查询散块直接暴力就行了。散块改完直接暴力重构。