杂题记录3
P8095 [USACO22JAN] Cereal 2 S
显然有一个二分图匹配 \(+\) 拓扑排序的做法,这里记录另一个巧妙的贪心线性做法。
考虑经典建图模型,我们对于一头奶牛建立一条边,边的两个端点分别为它最喜欢的麦片和次喜欢的。
每条边要去贪心地匹配其某个端点。多条边可能共端点,所以我们需要对于每个联通块都贪心一下。
对于每个联通块答案都有一个上界就是 \(\min(|E|,|V|)\)。
下面证明这个上界可以构造出来,要分类讨论一下 \(|E|\) 和 \(|V|\) 的大小关系。
- 树
此时满足 \(|E|<|V|\),也就是说我们需要对于每条边都找到一个点来匹配。可以发现限制最宽松的地方是叶子节点向上的那一条边,因为叶子节点只连了一条边,所以没边和这条边抢点,就算其上方端点被抢了,它也总能至少匹配到下方端点。因此我们只需要贪心地让上方边选就行了。dfs 一下,根据遍历到边的顺序将边放入排列即可。
- 图
此时满足 \(|E|\ge |V|\)。我们需要每个点都能有边被匹配到。还是根据上面的思路,我们要优先满足上方边。一个很直接的思路就是我们随机找一条边,然后去满足这条边的 \(f_i\)。以 \(f_i\) 为根建立 dfs 树(以 \(f_i\) 为根的原因是上方点被选择了,不会导致下方边无可选的点,和树的思想一样),在 dfs 树上又能满足 \(|V|-1\) 条边,一共就是 \(|V|\) 条边达到了上限。可是有一个问题,就是我们第一次选择的边不能出现在后面的那个 dfs 树里,如果我们选择了一条割边,那后续就无法生成 dfs 树了。直接 tarjan 找非割边太麻烦了,可以先随便建立一颗 dfs 树,此时非树边就必然是非割边了,随便抽条非树边出来跑上述算法就行了。
时间复杂度 \(O(n+m)\)。
XYD20935.小 W 与铁路
让两个人同时往前走,记录状态 \((i,j)\),表示处于哪个点肯定会走重。
如果按照 DAG 每次剥离零度点,对于图进行分层,让两个人尽量处于同一层中,每次都是进入下一层,这样就就可以让二人同步,不可能走到另一个人之前走过的点了。
上述做法等价于按照拓扑序进行转移,每次选择拓扑序小的点往前走一步。
二维 dp,时间复杂度 \(O(n^2)\)。
QOJ4212. Brackets
对于左括号出现的位置序列 \((p_1,p_2...p_n)\),括号序列合法当且仅当其被 \((1,3...2n-1)\) 偏序。可以看成一个匹配问题。
设序列中元素相同的位置分别为 \(i\) 和 \(nxt_i\),维护一个 std::set 里面是 \(1,3..2n-1\),只需要 \(i\) 和 \(nxt_i\) 都能在其中分别找到一个比它大的数,那么它们都可以放左括号。
从左往右做,能放左括号就放左括号,然后在 std::set 中删除对应匹配位置即可。
AH省集 D1T2 奇安凡尼银行
可以发现我们确定了达到某个末状态的最小操作次数之后是可以确定其能否被操作出来。
对于 \(n\) 是偶数的情况,可以发现转一圈和左右交替交换都不会改变奇偶性,于是只要操作数 \(\le m\),且和 \(m\) 同奇偶就可以完成。
根据糖果传递那题的结论,设 \(a_i-\rm ave\) 的前缀和为 \(s_i\),我们最后的最小代价是 \(\sum|s_i-x|\),其中 \(x\) 为 \(s\) 的中位数。\(s\) 序列和 \(a\) 序列是一一对应的,因此我们只需要对于 \(s_i-x\) 序列计数即可。枚举其绝对值之和(也就是总操作数) \(i\),\(<0\) 的数的个数 \(j\) 和 \(0\) 的个数 \(x\)。暴力做是 \(O(n^3)\) 的。枚举两个变量做两次就是 \(O(n^2)\) 的。
对于 \(n\) 为奇数的情况,除了上述答案之外。还有转了一圈之后,导致操作数的奇偶性变化了的选择。这个时候直接套用转一圈的情况是 \(m-n\) 次操作上界。但是其实可以更优,如果我们不选择转圈,而是选择在上面不用最优中位数来操作,用 \(\pm 1\),可以发现这个时候最优操作数变化量为 \(\max(-n_0+n_{+}-n_{-},-n_0-n_{+}+n_{-})\),也就是变成了 \(m-n+2\max(n_+,n_-)\)。在偶数的时候使用这个调整是不优的,因为操作数的奇偶性不会改变。
从函数图象上可以很好理解,\(n\) 段绝对值函数叠加,\(n\) 为偶数的时候,每段斜率为偶数,\(n\) 为奇数的时候,每段斜率为奇数。所以后者 \(x\) 每变化 \(1\),结果的奇偶性都会改变。
AH省集 D2T1 洞穴
考虑将限制放到局部来看,就是任意两点可以最后重合,且任意点都能到达 \(R\)。这显然是一个必要条件,注意到这个条件很强,尝试推广到充要条件,由于你是图上任意两点都满足这个限制,所以不管你怎么移动到新的点上,任意两个点还都可以继续合并,而且由于所有点都可达 \(R\),所以你将 \(n\) 个点合并成一个点之后,不管在哪里最后都是可以继续走到 \(R\) 的。
因此你直接建 \(n^2\) 个点表示二元组 \((u,v)\),二元组之间建边转移。最后能到达 \((a,a)\) 就是胜利。每次挑选两个点出来合并,总的时间复杂度是 \(O(n^3)\) 的,但是卡不满可以通过。
P5540 [BalkanOI 2011] timeismoney
将一颗生成树看成一个状态 \((\sum a_i,\sum b_i)\),显然一棵树的权值只与其状态有关。
将这个状态看成 \((x,y)\) 放到平面直接坐标系上,由于是要求 \((\sum a_i)\times (\sum b_i)\) 最小,也就是 \(x\times y\) 最小,所以可能成为答案的点一定在下凸包上面。
在 \(V\times V\) 的矩形内,凸包上点的个数是 \(O(V^{\frac{2}{3}})\) 级别的。
根据这条引理,有用的生成树其实很少,如果我们能只遍历这些生成树,那么复杂度就是正确的。
可惜我们无法直接显式建立这个凸包,但是我们知道凸包上的两点 \(P\) 和 \(Q\),分别是 \(\sum a_i\) 最小的生成树和 \(\sum b_i\) 最小的生成树。我们可以通过已有的两点去构建这个凸包,直接寻找所有点还是不容易,考虑哪个点是一定在凸包上的,那么就是一个点 \(R\) 满足在直线 \(PQ\) 下方且离 \(PQ\) 距离最大。找到 \(R\) 点之后更新答案,并且分治为 \((P,R)\) 和 \((R,Q)\) 去解决问题,这样子我们每次找到一个肯定在凸包上的点,总的寻找复杂度也就是凸包上点的个数乘以单次寻找时间复杂度。
现在考虑如何找到 \(R\) 点,上述条件等价于三角形 \(PQR\) 的面积最大,也就是 \(\vec{RP}\times \vec{RQ}\) 最大。代入坐标,利用叉乘公式计算之后可以得到,希望最小化 \((X_Q-X_p) y_R+(Y_Q-Y_P)x_R\)。下面一步很巧妙,我们直接把每条边的边权设置为 \((X_Q-X_p) b_i+(Y_Q-Y_P)a_i\),然后跑一遍 Kruskal 即可找到 \(R\) 点。递归下去即可找到所有点。
注意每次找到一个点后,需要用叉乘判断其是否在凸包上,否则会超时。
记 \(V=\sum a_i\),时间复杂度为 \(O(V^{\frac{2}{3}}m\log m)\)。
时间复杂度 \(O(n+m)\log n\)。
QOJ9840.Tree Partition
直接进行树上背包,显然复杂度太高且无法刻画联通块编号连续这个信息。
考虑转化一下,我们在值域上进行这个 dp,每次提取一个值域连续段,要求其在树上构成一个联通块。对于一个点集,可以通过点减边为一来判定其为一个联通块。于是我们直接 dp,利用线段树维护转移点到目前点构成区间的点减边数量,同时注意到点减边最少也得是 \(1\),于是我们就直接记录最小值位置的 dp 值之和,进行转移即可,时间复杂度 \(O(nk\log n)\)。
发掘一些性质,我们给树定向,从儿子指向父亲。放到值域上,就是若干前向边和后向边。发现最多可以"浪费一条边"(向区间外部连),也就是这个联通块的根向父亲的边,其余的边必须在这个区间内部。
于是我们可以得到判定条件就是,扫描到 \(i\) 的时候,对于 \(fa_j>i\) 的 \(j\) 最近和次近的 \(j\) 分别为 \(x,y\)。那么 \(j\in [y,x)\) 能转移到 \(i\) 的条件为 \([j,i]\) 没有连到 \([1,j)\) 的边。这是对于后向边区间的判定条件。对于前向边区间类似判断即可,可以先 \(O(n\log n)\) 用数据结构维护出来转移点,然后再 dp 一遍 \(O(nk)\) 解决。
P12503 「ROI 2025 Day1」索契游乐园
不错的函数最优化练习题。考验选手卡常技巧。
首先,对于一个选手来说向左走和向右走是独立的。有两种策略,一个是向左来回走一趟然后向右,另一种是向右来回一趟再向左。直接把来回的权值设成 \(2w\) 即可。然后两种策略的答案取 \(\min\) 即可。
于是经过上述转化,我们只需要平移坐标然后考虑从 \(x_0=0\) 开始的单方向行走就行了。一个最直接的策略就是枚举走到了哪个位置 \(x\),有一个行走的能量,\(w\times x\),已经被覆盖的点 \(c_i\) 消耗的能量就是 \(\min((c_i\bmod d)^2,(c_i-c_i\bmod d)^2)\),未被覆盖点消耗的能量是 \((c_i-x)^2\)。于是答案就是
记录一些前后缀和可以 \(O(\log n)\) 计算这个式子,带 $\log $ 的原因是你需要二分出 \(c_i\le x\) 的分界点 \(i\)。
这个式子是一些二次函数状物构成的,所以我们猜测其具有凸性,且是一个单谷函数。
于是可以通过直接三分来找到极值点,单次计算 \(f(x)\) 的代价为 \(O(n\log n)\)。所以总的时间复杂度就是 \(O(m\log n\log V)\),可以喜提 \(87\rm pts\)。
发现复杂度的瓶颈是在于每次三分内部要要二分出是在哪一段。于是我们可以先三分出在哪一段,然后段内再三分出哪个是极值点,这样子第一次计算的时候取点我们直接取段内的左右端点即可,第二次计算的时候已经确定段了就不需要再二分段了。时间复杂度也可以做到 \(O(m(\log n+\log V))\),可以获得 \(91 \rm pts\)。
不过其实最后确定段之后,其中一个变量分界点 \(i\) 已经确定了,于是这就是一个纯的二次函数,我们可以直接求出二次函数极值点,然后在附近找到 \(d\) 的倍数看看谁最优即可(注意特判二次项系数为 \(0\))。于是又优化掉一个 \(\log V\)。时间复杂度 \(O(m\log n)\),还是 \(91\rm pts\)。
其实整数域的三分是可以用二分替代的,因为下凸函数满足差分单调递增,我们直接二分出差分值在 \(0\) 附近的点即可。这样子能大幅度减少常数,获得 \(100 \rm pts\)。
当然由于我的写法比较丑陋,所以还需要加入下面若干常数优化。
-
可以发现随着 \(w\) 的增大,我们的最优行走距离 \(x\) 是单调递减的,所以我们可以对于询问的 \(w\) 进行排序,然后每次就可以缩短二分上界了。
-
虽然是 \(O(1)\) 计算函数值,但是由于计算过程中涉及
__int128,所以下面代码中的 \(g\) 函数的计算会异常地慢,可以发现由于 \(g\) 是段的函数值,一共只有 \(O(n)\) 段,我们可以提前算出这些值,然后直接用即可。
QOJ8706. 解方程
很妙的一题,注意到后面那一坨实在难以处理,但是数据随机生成,所以考虑进行一步放缩,把最后的一部分放缩掉,我们可以得到 \(a_ix\in [c_i-10^{11},c_i]\),数据随机情况下所以我们选择前两个方程,可以保证满足这两个约束的 \(x\) 的个数大约为 \((\dfrac{10^{11}}{P})^2\times P=10^4\) 这个量级,这样子我们只要找到所有满足条件的 \(x\),再验证 \(m\) 个方程即可。
如何找到所有符合条件的 \(x\),BSGS!具体来说就是,我们先把第一个方程调整成 \(x\in [0,R]\) 的形式(这一步可以令 \(x\gets a_1x-(c_1-10^{11})\),最后再变回去)。然后对于第二个方程是 \(a_2x\in [c_2-10^{11},c_2]\),令 \(B=\sqrt {10^{11}}\),预处理 \(x\in [0,B]\) 的 \(a_2x\bmod P\) 排序。枚举 \(i\in [0,B]\),对于 \(a_2iB\) 在排序数组中二分找到所有满足条件的数,组合出 \(x\) 之后进行 check。之前说了满足条件的 \(x\) 的期望个数为 \(1000\),所以 BSGS 暴力找到所有解并且检验的复杂度是正确的。
除了上述讲的做法,还存在一种类欧解法。
P12796 [NERC 2022] Game of Questions
本题的重点在于观察到:一个问题最多被提问一次,如果提问第二次是不会产生任何效果的。于是我们并不关心哪些问题已经被提问过了,只关心哪些问题可以使得当前集合发生变化,\(s\to t\) 的时候拿发生 \(s\to t\) 变化的问题的种数除以能使得 \(s\) 发生变化的问题数就是单步的概率。
同时除了一个问题被问两次之外,我们还要思考是否会出现有一个问题一次也没有被问。由于上面说了,每次集合之间的转移是考虑能使得当前集合发生变化的问题,那么在某条转移路径中没有被列举的问题就是对于单步不产生影响的,问不问也不影响某个变化过程中的概率。
设 \(g_{S,T}\) 表示 \(S\to T\) 的方案数,那么对于 \(f\) 的转移有 \(f_T\gets \dfrac{f_Sg_{S,T}}{n-g_{S,\emptyset}-g_{S,S}}\)。
接下来要求解 \(g_{S,T}\),由于 \(T\subset S\),所以这部分的空间是 \(O(3^n)\) 的。
设问题 \(i\) 能回答出来的人的集合为 \(s_i\),那么 \(g_{S,T}\) 为 \(s_i\cap S=T\) 的 \(i\) 的个数。唉我怎么不会求。考虑增量法,令 \(x\) 为任意一个不在 \(S\) 中的元素,那么 \(g_{S,T}=g_{S\cup \{x\},T}+g_{S\cup \{x\},T\cup \{x\}}\)。观察这个递推形式,考虑按照 \(S\) 从大到小推,每个 \(S\) 内部要按照 \(T\) 从大往下推。所以需要利用 \(s_i\) 信息得到所有的 \(g_{S,S}\) 就可以推全局了,也就是 \(S\subset s_i\) 的 \(i\) 个数,直接超集求和即可。
于是就可以快速递推出 \(g\) 数组。
时间复杂度 \(O(3^n)\)。
以下为未完成的题解
P10559 [ICPC 2024 Xi'an I] The Last Cumulonimbus Cloud
有意思,图上邻域问题根号分治是经典解法,本题没卡,但是实际还有更优做法。
ZROI3083.Xorshift
复杂题逐步分析。这个 \(3\) 操作很复杂,大概是线性基自由元之类的,所以肯定要用上 \(A\) 性质。
考虑 \(AD\) 搭配,可以发现由于没有输入的 \(x\),所以初始值是固定的,我们可以时刻维护当前 \(f\) 的值,如果遇到了 \(g\) 操作,就可以直接模拟出 \(g(f(a_i))\) 的值。不需要每次回答都从头做一遍。时间复杂度 \(O(nm)\) 可以过 1-5。
对于 14,15 没有 \(D\) 性质,可以 \(O(nm^2)\) 模拟。
发现瓶颈在于修改,这个函数复合太难做了,很难整体做出什么事来。
考虑一下 \(C\) 性质 \(z=0\),有一个很 nb 的观察就是 \(g(x\oplus y)=g(x)\oplus g(y)\),于是我们只需要维护全局异或值即可。去掉 \(C\) 性质呢?无非多判断一下操作次数的奇偶来决定是否带上 \(z\)。
QOJ5092.森林游戏
多个单点,就是对于点权排序。考虑长度为 \(2,3\) 的链模拟一下,
CF1511G Chips on a Board
即求 \(\bigoplus\limits_{l\le a_i\le r}(a_i-l)\)。
很反直觉啊,其实这个区间信息是可以合并的。
设 \(f_{i,j}\) 表示 \(\bigoplus\limits_{i\le a_k<i+2^j}(a_k-i)\),\(g_{i,j}=\sum [a_k\in [i,i+2^j)]\)。
首先有,\(f_{i,j}\gets f_{i,j-1}\)。其次考虑 \(f_{i+2^{j-1},j-1}\) 的贡献,拆位角度来思考,可以发现其实是被多减去了一个 \(2^{j-1}\),我们需要补上这一位的贡献,那么就是通过 \(g_{i+2^{j-1}.j-1}\) 的奇偶性来决定。
求解出 \(f_{i,j}\) 之后,我们需要对于区间 \([l,r]\) 求答案。
WC2025 士兵
先想贪心,发现根本不可做。因此考虑 DP。
思考一下
P11363 [NOIP2024] 树的遍历
好题啊。不妨从菊花图入手,可以发现对于一个点,其周围的边生成树是一个链状,这是因为我们从一条边出发不断到某一条未遍历的边。转化到图上也就是说对于一个点周边的一些边也是类似的遍历情况,就是我们逐步遍历所有点,每次通过一条边到下一条边,虽然它们下方还有边,但是那些下方的边不影响,因为下方的边永远不可能跨过当前的边来到上面。所以对于一个点其周围的遍历顺序就是一个排列,由于第一个被遍历的边已经确定了,所以就是 \((deg_u-1)!\)。
求一下 \(\prod\) 就是 \(k=1\) 对应的情况。
考虑一下 \(k=2\) 的情况,先累加两倍 \(k=1\) 的答案。然后我们需要减去从两条边出发得到的相同的生成树。发现其中的关键就在于两条边之间的链,假设是 \(L\to...\to R\),我们从左边

浙公网安备 33010602011771号