XYD-省选 | 思维题 2

UOJ424. count

对于区间最大值位置本质不同的序列计数,可以直接转化为笛卡尔树计数。

考虑如何判定合法的笛卡尔树,左子树的值需要 \(<\) 根,右子树需要 $\le $ 根。上述约束需要满足左子树深度 \(\le m\),笛卡尔树是二叉树,可以转化为括号序列计数。

首先 \(n<m\) 肯定无解,可以猜测 \(m\le n\) 的时候对于合法的笛卡尔树一定存在构造使得 \([1,m]\) 都出现过一次,这个很好感性理解。

因此我们现在就是要求从 \((0,0)\) 点出发,每次可以向右下或者右上走一步,不碰到 \(y=-1\)\(y=m+1\) 的方案数。

直接用反射容斥就解决了。

UOJ693. 【UR #23】地铁规划

本质上是用单栈维护原本的双栈维护的不带删双指针。

先回顾一下双栈,左栈中从上到下为从左到右,右栈中从下到上为从左到右。每次将 \(l\) 右移就是等于说是弹出左栈栈顶,将 \(r\) 右移就是加入右栈栈顶。左栈空了就重构。

记录原先左栈中的边为 \(0\) 边,右栈中的边为 \(1\) 边。其中 \(0\) 边要求其被倒序加入。

使用单栈之后,右移 \(r\) 就是加入若干 \(1\) 边,左移 \(l\) 就是先让栈顶为 \(0\) 边,然后 undo

\(r\) 的移动过直接做就行了。\(l\) 的时候一个很暴力的思路就是不断弹出 \(1\) 边直到遇到了 \(0\) 边,但是这是很不优的,因为如果为了一个 \(0\) 边而遇到了许多 \(1\) 边的话,就会被卡爆。为了不白白弹出这些 \(1\) 边,我们考虑你前面如果弹了很多 \(1\) 边,必然会导致后面的 \(1\) 边少,所以不如就是多搞点 \(0\) 边出来。于是我们就直接弹栈,直到弹出来的 \(0\) 边和 \(1\) 一样多。然后把 \(1\) 边全部都按照原顺序放进去,再把 \(0\) 边丢进去,这样子使得很多 \(0\) 边都上移了。

可能开始会有疑惑,为什么不把 \(1\) 边倒着塞回去,使得 \(1\) 边变成 \(0\) 边,但是需要注意 \(0,1\) 本质上是两个独立的栈,只有把 \(0\) 栈全部弹出来之后,再翻转 \(1\) 边才是正确的。

上述弹栈过程注意 corner case,当没有 \(0\) 边的时候就直接把所有 \(1\) 边翻转了。如果所有边都被弹出来之后还没有两者数量相等,那就可以把所有 \(1\) 边倒着加进去了。这个做法是根号的。

采用二进制分组法,我们对于 \(0\) 进行二进制分组,比如按照 \(1,4,8\) 为组的大小进行划分。每次弹出一组之后就停止,也就是弹 \(\rm lowbit\)\(0\) 边。对于 \(1\) 边可以发现在栈中形成了 \(\log\) 个连续段,每次弹出一个连续段,因此每个元素至多被操作 \(\log\) 次。对于 \(0\) 边,每次操作之后所在组的大小至少变成 \(\dfrac{1}{2}\),比如 \(8\to 1,2,4\),所以也至多被操作 \(\log\) 次。

QOJ2562. Fake Plastic Trees 2

有一颗带点权的树,对于 \(k\in [0,K]\),判断是否可以删去树上的 \(k\) 条边,使得剩下的每个联通块点权和在 \([L,R]\) 之中。\(n\le 10^3,K\le 50,l,r\le 10^{15}\)

暴力 dp 就是设 \(dp_{i,j,k}\) 表示 \(i\) 子树内断了 \(j\) 条边,能否达到权值 \(k\)。然后跑一下树上背包。

第三维度考虑用 std::vector 保存所有可能的状态。但是状态数太大了,我们需要对于状态剪枝。由于最后只需要判断是否存在 \([L,R]\) 之内的权值,于是对于三个状态 \(x<y<z\),当 \(z-x\le R-L+1\) 的时候,\(y\) 是无用的。直接不用保存即可。

如果一个联通块内取值不足 \(L\) 是要一直选择的,这一直是一个状态不会增多。到了 \([L,R]\) 之内可以闭合该联通块,因此初始状态只会扩展出 \(O(R-L)\) 个新状态。又因为状态数要除以 \(R-L+1\),所以对于一个初始状态只会扩展出 \(O(1)\) 个状态,扩展了 \(k\) 次也就最多出现 \(O(k)\) 个状态。因此对于 \(dp_{i,j}\) 其第三维的大小为 \(O(j)\)。总的时间复杂度 \(O(nk^3)\)

P8375 [APIO2022] 游戏

还是要利用性质,经典算法可扩展性差。

\(k=1\) 就是维护每个点和 \(1\) 点的双向可达性。可以暴力 dfs,我们加入一条边 \((u,v)\) 的时候,对于 \(u\),看看 \(v\) 能否到达 \(1\),如果能的话就更新 \(u\to 1\) 的可达性。对于 \(v\) 看看 \(1\) 能否达到 \(u\),如果能的话,就更新 \(1\to v\) 的可达性。一个点的可达性如果更新之后就暴力沿着图更新这个信息,可以发现每个点只会被标记最多两次,因此时间复杂度就是 \(O(n)\) 的。

同理对于 \(k\) 个点可以直接 \(O(nk)\)。对于某点只要能到达它的的最大编号关键点的编号大于它能到达的最小编号关键点的编号就代表出现了环。

考虑维护 \(l_i\) 表示 \([1,l_i]\) 可达 \(i\),维护 \(r_i\) 表示 \(i\) 可达 \([r_i,n]\),考虑维护区间 \([l_i,r_i]\)。当 \(r_i\le l_i\) 的时候就出现了环。对于新加入的一条边 \(u\to v\),可以发现当两个区间重合的时候是无影响的,非包含的时候如果 \(u\) 区间在 \(v\) 左边也是无影响的,如果 \(u\) 区间在 \(v\) 区间左边就产生了环。对于包含情况是不会产生环的,但是会更新一些 \([l_i,r_i]\)。把这些区间放到线段树区间上去,并且维护通过 \(\rm mid\) 的可达性,发生单侧的变化的时候递归到下一层即可。

QOJ8047.DFS order 4

由于可能多颗树能得到相同的 dfs 序,所以我们可以来考虑有多少种 dfs 序可以还原到一颗合法的树。需要判定序列合法性。

如何判定一个序列合法?我们贪心的把点往一条父链上挂。如果 \(p_i>p_{i-1}\),那么可以挂在这条链末尾 \(p_{i-1}\) 的下方。如果 \(p_i>p_{i-1}\),我们可以在这条链上找到一个最浅的祖先满足 \(p_k<p_i\),我们把 \(p_i\) 挂在 \(p_k\) 的父亲上(这相当于 dfs 的回溯过程),这样子既满足兄弟节点编号递增,也满足了儿子大于父亲。这样子排列就和树一一对应了。

这样子贪心连出来的树等价于以下限制:每个点编号大于父亲,每个点儿子编号单调递增,且小于上一个兄弟的最后一个儿子。

可以发现第 \(2\)\(k\) 个兄弟节点和父亲间的约束没有用,可以通过 \(son_2>son_1>fa\) 来得到。直接删掉就行了,只有在兄弟之间连边。

有一个结论就是一颗外向图的拓扑序个数是 \(\dfrac{n!}{\prod sz_i}\)。但是本题由于有了小于上一个兄弟的最后一个儿子这个限制,导致这不是一颗外向树。

考虑容斥掉这条边,也就是 \([u<v]=1-[u>v]\)。等号右边的两种情况都是一棵树,第一种情况系数是 \(+1\),第二种情况系数是 \(-1\)

现在需要分析一下新构建的外向树长什么样子。从父子关系角度考虑,有如下三种:一个父亲和它的第一个儿子,相邻兄弟,上一个兄弟的最后一个儿子和这个点。

现在考虑在一层兄弟链上面考虑,其中不止有第二类边,还有第三类边,也就是中间会夹杂着若干个上一个兄弟的一堆儿子。在链上如果走到了下方的节点,根据上述边分类,可以发现这是属于 \(-[u>v]\) 这种边的需要乘上一个 \(-1\) 的系数。

\(d_i\) 表示 \(sz=i\) 的子树的贡献,在链上挂子树的时候算贡献。\(f_{i,k}\) 表示 \(sz=i\),在链上深度走到了 \(k\) 的方案数。

首先有 \(f_{i,0}=g_i\)

合并子树,\(f_{i+j,k}\gets f_{i,k}\times d_j\times\dfrac{j}{i+j}\)

往下走,\(f_{i+j,k+1}\gets -d_j\times f_{i,k}\times\dfrac{j}{i+j}\)

往上走,\(f_{i+1,k-1}\gets f_{i,k}\times \dfrac{1}{i+1}\)

直接在一条链上往上连一个点,\(d_{i+1}\gets f_{i,0}\times\dfrac{1}{i+1}\)

注意一下细节,我们是按照 \(sz\) 从小往大处理,所以有些 \(f_{i,k}\times d_j\)\(d_j\)\(i\) 处还没有被算到,需要在 \(j\) 处再处理一下。

答案就是 \(d_n\)。时间复杂度 \(O(n^3)\)

UOJ750.小火车

可以 meet-in-middle,不过底数为 \(3\),没有前途。可以去冲随机数据,根据生日碰撞,我们在两边各选 \(O(\sqrt p)\) 个集合进行匹配就行了。

这题本质是找出两个不交集合,使得两者在模 \(p\) 意义下和相等,一个赋 \(1\),另一个赋 \(-1\)。可以通过 \(2^n\) 枚举出所有集合放到桶里面匹配。如果有两个集合有重复元素,删去之后两个集合还是相等的。由于 \(p<2^n\),也就是子集的个数大于桶的个数,所以一定有解。于是就优化到了 \(O(2^n)\)。不过这还不够,我们优化的方向是把指数规模缩减为 \(\dfrac{1}{2}\)

考虑我们其实只需要找到一个元素个数 \(\ge 2\) 的桶,并不需要得出全部信息。因此可以类似于线段树二分的结构,每次寻找 \(r-l+1<cnt_{l,r}\) 的区间走。求 \(cnt_{l,r}\) 可以直接 meet-in-middle 并用双指针优化。时间复杂度 \(O(2^{\frac{n}{2}}\log p)\)

UOJ449. 【集训队作业2018】喂鸽子

j将投喂序列中每个元素出现的前 \(k\) 次全部提取出来,构成一个有用序列。

考虑直接对于一个长度为 \(nk\) 的有用序列进行统计。我们需要统计的量就是它的概率和期望长度,知道这些信息之后就可以直接求答案 \(E(X)=\sum p_x\times l_x\)

概率就是每个位置当前能被出现的元素种类分之一,也就是没达到 \(k\) 的元素个数,把这些概率乘起来就是这个序列成为有用序列的概率。

关于期望长度就是可以在每个位置前面出现一些不在有用序列里面的元素,也就是一些在之前就已经达到 \(k\) 的元素再重复出现就不再放到有用序列里了。这个系数和之前已经达到 \(k\) 的元素个数有关。

于是考虑 dp,可以发现唯一的约束条件就是之前达到 \(k\) 的元素个数,放到状态里面直接设 \((i,j)\) 二维状态就行了。

有点细节,由于 \(l\times p=(\sum l_i)\times (\prod p_i)\),我们需要每个 \(l\) 被全局的概率都乘一遍。所以设 \(f_{i,j}\) 表示前 \(i\) 个位置里面有 \(j\) 种元素达到了 \(k\) 的贡献,\(g_{i,j}\) 表示前 \(i\) 个数里面有 \(j\) 个达到 \(k\) 个的方案数 \(\times\) 概率。之所以要带上方案数,是是因为我们要对于所有情况求和,相同的方案合并就是一个方案数。我们每次加入一个数不考虑方案数,因为不好控制一个数的出现次数,统一在第 \(k\) 次出现的时候,在前面 \(i-1-(j-1)\times k\) 个位置里面选 \(k-1\) 个位置与当前位置组合,也就是 \({i-1-(j-1)\times k\choose k-1}\times (n-(j-1))\)

每次要让之前的数乘上现在的概率也就是 \(p_jf_{i-1,j}\),当前这一位的贡献是新增的期望长度乘以之前所有的一路的概率 \(E_jg_{i,j}\)

对于 \(j-1\to j\) 的转移也是同理,不过需要乘上一个组合数的系数。

转移复杂度是 \(O(1)\) 的。于是时间复杂度就是状态数 \(O(n^2k)\)

QOJ8553.Exchanging Kubic

先用 \(n\) 次找到所有正负位置以及正位置的权值 ++---+++---+-

我们将符号相同的连续段合并,这样的话就可以得到一个正负交替的序列,并且我们知道所有正数的值。

对于 +-+ 进行询问,一种情况是得到三个数之和(也就是返回值和两个正数段都不同),这样子可以直接得到负数的权值,于是直接将这三段合并成一个正段。一种情况是两个正的最大值,这样子我们可以知道负数的绝对值是大于两个正数中的小者。那么我们索性就直接从全局正段最小值开始做,询问其两边的正负正结构,如果都不行的话就把它和周围两个负段合并起来。

posted @ 2025-02-23 20:18  Mirasycle  阅读(66)  评论(0)    收藏  举报