2024.8.26 近期练习
P3349 [ZJOI2016] 小星星
我们想到状压 dp,设 \(dp_{S,u,rt}\) 表示 \(u\) 子树由 \(S\) 集合的点构成,根节点是 \(rt\) 的方案数。
这样的话,转移需要枚举子集,是过不了的。这里应该使用容斥。
尝试把编号是一个 \(1\sim n\) 的排列这一条件去掉,就不用记录 \(S\) 了。
那么我们定义 \(dp_{u,rt}\) 表示 \(u\) 子树,根节点是 \(rt\) 的方案数,那么怎么转移呢?
考虑容斥,枚举 \(\{1,2,...,n\}\) 的子集,然后做 dp,强制编号都在其里面,求方案数,出来容斥即可。
dp 复杂度是 \(O(n^3)\),总的复杂度 \(O(2^nn^3)\)。
用容斥是因为保证每个标号都存在很难,但是删掉某个标号的点很简单。
AGC022E
第一步是找最后剩下的是 \(1\) 的条件。如果有连续的 0,那么一定给他删掉,最后剩下 0/1/2 个。
我们想一个删的策略,使得这样删下去能把合法的都删成 \(1\)。
那么,我们从左往右删,能用左边的 \(1\) 就用,记录右边还要用多少个 \(1\)。
设 \(dp_{i,j}\) 表示填到第 \(i\) 位,欠了右边 \(j\) 个 \(1\) 或左边多出 \(j\) 个 \(1\) 的方案数,然而状态数是 \(O(n^2)\)。
然而这么做很蠢,我们发现,不可能欠右边那么多的 \(1\),因为连续的 0 是可以再次消掉的。
如果欠 \(\ge 3\) 个 \(1\),说明有 000 出现,应直接消去。
同时不可能多出那么多 \(1\),因为如果有 \(\ge 3\) 个 \(1\),那么无论如何都可以消去。
要注意的是 0 个数量和 1 的数量都要记录在状态里面。
ARC093F
我们假设已经知道了 \(1\) 的所有对手,那么这些人都是对应子树里的最小值,考虑计数。
我们标号从大到小,设其对应的子树大小为 \(s\),标号为 \(i\),之前用了 \(cnt\) 个,累乘 \(A_{n-i-cnt}^s\)。
考虑容斥,因为 \(m\) 比较小,我们枚举不合法的人数,然后容斥原理。
我们考虑状压 dp 求出 \(f(k)\) 表示选了 \(k\) 个不合法的人的方案数。
不妨设 \(dp_{S,i}\) 表示 \(S\) 大小的子树已经钦定为不合法,当前从大到小选到第 \(i\) 个不合法的人的方案数。
转移的话就是乘上 \(A_{n-i-cnt}^s\)。
那么,合法的子树贡献怎么办呢?其实无论怎么排都是合法的,对于 \(dp_S\),我们最后乘上 \((n-S-1)!\)。
P10764 [BalticOI 2024] Wall
如果墙的高度只有 1/2,那么积水的量是最后一个 2 的位置-第一个 2 的位置+1-2 的个数。
考虑拆贡献了,我们计算每个高度的答案,可以把 \(a,b\) 揉起来排序后,计算每个区间的答案。
我们考虑计算第 \(x\) 列,水高 \(h\) 时的答案,即 \([1,x]\) 最大值 \(> h\),且 \([x,n]\) 最大值 \(> h\) 的方案数。
这个用线段树计算,递推水的高度,每次只是单点修改。
然而 \([1,x]\) 最大值 \(> h\) 这类条件不好算,考虑容斥,\([1,x]\) 最大值 \(\le h\) 就好算了。
P3214 [HNOI2011] 卡农
题意是转化为有 \(1\sim 2^n-1\) 这些数,选择 \(m\) 个数出来,两两不同。
每个音阶被奏响的次数为偶数转化为这 \(m\) 个数异或起来为 \(0\)。
考虑先计算 \(f_i\) 表示选了 \(i\) 个数满足题意的有序集合个数。
我们假设已经选出前 \(i-1\) 个数,那么方案数是 \(A_{2^n-1}^{i-1}\),设其异或和为 \(x\)。
若 \(x\neq 0\),第 \(i\) 个数直接填 \(x\) 即可。
若 \(x=0\),那么这是不合法的,方案数就是 \(f_{i-1}\),要减去。
还有一种情况就是 \(x\) 之前出现过,那么把之前和现在的 \(x\) 一起去掉,剩下 \(i-2\) 个是合法的。
出现过的 \(x\) 的位置有 \(i-1\) 种,以及这个 \(x\) 的取值有 \((2^n-1)-(i-2)\) 种,再和 \(f_{i-2}\) 乘起来减掉。
注意我们这里要求的是无序集合,最后除以 \(m!\)。我们转成有序集合求是方便很多的。
上面那个 \(A\) 的计算直接循环 \(m\) 次算即可。
P3244 [HNOI2015] 落忆枫音
如果是单纯的一个 DAG 答案就是除了根每个点入度之积,即其父亲。
现在加了一条边形成环,我们考虑减掉形成环的情况,除了这个环上的点,其他点不变。
考虑计算有环的方案数,新加的边一定是必选的,如果加的是 \(s\to t\),那么 \(t\to s\) 一定有路径。
考虑从 \(t\) 到 \(s\) dp,设 \(f_x\) 表示 \(t\to x\) 的所有路径的贡献之和,对于路径一个点,我们要除去其入度。
所以,\(f_x=\frac{1}{deg_x}\sum_{y\to x} f_y\).
P3750 [六省联考 2017] 分手是祝愿
首先我们可以得出一种关灯的策略,从大到小关,如果亮着就关掉。
那么一个局面可以归类成若干个灯要关,且必须关这些灯,其他的无所谓。
因为所有灯等价,我们可以设 \(f_i\) 表示当前如果还剩 \(i\) 个灯,还要关多少次。
做一个链上高消即可。
P8290 [省选联考 2022] 填树
首先肯定是填一条链出来。这个是可以树形 dp 的。
我们枚举一个区间 \([x,x+K]\),钦定所有值在其中,并容斥减掉 \([x+1,x+K]\) 的答案即可。
每个点的取值是 \([\max(x,l_i),\min(x+K,r_i)]\)。
我们发现我们是无法绕过枚举 \(x\) 的限制的。
但是当我们移动 \([x,x+K]\) 这个窗口时,每个点的 \([\max(x,l_i),\min(x+K,r_i)]\) 都是均匀的变化的。
但是会发生 \(O(n)\) 次改变,这 \(O(n)\) 次改变来源于 \(l_i,r_i,x,x+K\) 之间的相对大小关系的改变。
如果是链,每个点的贡献是 \(\min(x+K,r_i)-\max(x,l_i)\),拆 \(\min,\max\),共有 \(O(n)\) 种取值。
我们发现我们要求就是关于 \(x\) 的 \(O(n)\) 次多项式,插值求其前缀和。
如果是树,我们发现还是 \(O(n)\) 次多项式,考虑插值,第一问是 \(n+1\) 次,第二问是 \(2n+1\) 次的。
总共做 \(O(n)\) 次插值,复杂度 \(O(n^3)\)。
Qoj # 7884. Campus Partition
题意:把树划分为若干联通块,使得每个联通块次大权值的和最大。
注意到:若一个联通块里最大和次大的是 \(x,y\),那么只需要保留 \(x\to y\) 的路径即可。
那么把题目转化为路径划分,且路径的权值就是两个端点最小的那个。
不妨设计树形 dp,维护 \(g_{u,x}\) 表示 \(u\) 子树内,\(u\to v\) 的路径被选,\(a_v=x\) 的答案。
同时计算 \(f_u\) 表示 \(u\) 子树内的答案。我们考虑一个个儿子加入,转移是比较显然的。
首先 \(f_u\) 加上 \(f_v\),其次 \(g_{u,x}\) 与 \(g_{v,y}\) 合并,加上 \(\min(x,y)\),更新 \(f_u\)。
之前的 \(f_v\) 之和为 \(\sum f_v\),那么 \(g_{u,x}+\sum f_v\) 与 \(g_{v,x}+f_u\) 更新取 \(\max\) 放到 \(g_{u,x}\)。
考虑用线段树合并来维护转移的过程。
CF840E
考虑到异或和加法之间的性质非常不优美,所以我们希望其能够都是同一类运算。
我们要进行的是诸如此类运算求 \(a_i\otimes (dep_i+x)\),你发现每次 \(x\) 变化后是很难求得。
所以,记下每个 \(x\) 的答案,但是 \(x\) 太大,所以记录 \(x\) 的后八位的 \(256\) 种情况,只用考虑前 \(8\) 位。
考虑分块,每个点维护其向上跳 \(256\) 个,这里面所有数的值,前 \(8\) 位用一个字典树去处理。
P4425 [HNOI/AHOI2018] 转盘
你要想一种标记物品的策略。观察到,你如果重复经过某个点,那么只需要最后一次过即可。
所以,最优解的其中一种是只需要绕不到一圈的。
所以断环为链,选一个起点,走一个长度为 \(n\) 的区间即可。
但是如何快速的计算时间是一个问题,如果起始点是 \(s\),那么就是求 \(n+\max_{i\in[s,s+n-1]}a_i-(i-s)\).
这个意思是把移动要花费的时间全部去掉。那么你现在要对所有 \(s\) 求最小值。
我们把答案改写成 \(n+s+\max_{i\in[s,s+n-1]}a_i-i\),现在可以每次 \(O(n\log n)\) 查询。
事实上,这个区间可以改成后缀 \(\max\)。所以我们就是要维护一个单调栈。
维护单调栈的问题参考楼房重建,我们 pushup 的时候,先递归右边,在二分出左边怎么接上。
P9598 [JOI Open 2018] 山体滑坡
问题是:只保留 \([1,x]\) 的点边和 \([x+1,n]\) 的点边分别连通块的个数和。这两个是独立的。
考虑操作分块,如同 APIO2019 桥梁那般。对于每 \(B\) 个操作进行计算。
前缀和后缀同理,我们现在计算前缀的答案。
考虑扫描线,把 \(x\) 排序后从小到大加入。然后加入连接 \(x\) 的边。但是不能动在这个块里有变化的边。
同时,对于每个询问,我们把询问的时间的当时还在的边加入,用完之后用可撤销并查集给撤销了。
P8990 [北大集训 2021] 小明的树
我们要想一个计算连通块个数的策略。我们不妨维护 \(f_{u,i}\) 表示 \(u\) 子树在第 \(i\) 时刻的贡献。
用线段树合并来维护。可以通过 \(30\) 分。现在考虑断边又连边之后怎么办?发现我们做法失败了。
你考虑转换限制,因为点亮的灯这个限制很丑陋,我们把他转化为没点亮的灯是联通的。
然后我们用经典技巧把数没点亮的树转化为点减边,若为 \(1\) 那么就联通。
考虑连接两个黑灯的边存在的时间:区间加即可。
考虑贡献,不难发现是连接亮灯和不亮灯的边的个数。这个也是区间加。
我们怎么统计总贡献呢?那么就是点减边最小值的所有位置的贡献和,线段树维护。
我们为什么要把所有计算写成边的形式呢?因为本题树的形态是变化的,每次操作都和边有关。
P3206 [HNOI2010] 城市建设
考虑分治。但是你发现只有当 \(l=r\) 时你才能去跑一遍生成树,因为边是要排序的。
但其实,你可以考虑在 \(l\neq r\) 的节点做一些优化。
第一,把 \([l,r]\) 之间的边先删掉,然后呢跑一遍生成树,此后没有加入的边就可以删掉了。
第二,强制加入 \([l,r]\) 之间的边,然后呢跑一遍生成树,此后加入的边就一定要被加入了。
那么,我们维护并查集,对于哪些一定要被加入的边,就并查集先合并上。
通过操作二,对于 \([l,r]\) 这个节点,存在的连通块数一定不超过区间大小,因为至多还有那些边尚加入。
同时,因为操作一,尚待确定的边数量与点同阶。
接下来,我们分治下去,只在右边不在左边、只在左边不在右边的边将变成静态的。
我们会把这些静态的边加入,与尚待确定的边一起,进行一和二操作。
需要使用可撤销并查集。口胡完毕,实现并不容易。
P3688 [ZJOI2017] 树状数组
我们必须转化一下题目所给的 Add 和 Query。
不难发现若 Add \(x\) 能贡献到 Query \(y\) 的条件是 \(x\ge y\),也就是求单点加,求后缀和。
所以现在每次询问就是计算 \([x,y]\) 的区间和跟 \([x-1,y-1]\) 区间和相等的概率。
也就是询问 \(a_{x-1}\) 和 \(a_y\) 相等的概率。我们很容易想到维护 \(a_{x-1},a_y\) 分别取值 01 的概率,
但是这是错的,因为他们并不是独立的。 这很关键。
所以我们只能把所有 \(x,y\),\(a_x=a_y\) 的概率取出。
考虑修改 \(l,r\) 对 \(x,y\) 的影响。设 \(p=\frac{1}{r-l+1}\)。
当 \(x,y\) 其中一个 \(\in[l,r]\) 的时候,会有 \(p\) 的概率改变;有两个时,会有 \(2p\) 的概率改变。
那么,这是一个矩形覆盖问题。需要树套树维护。
P8476 「GLR-R3」惊蛰
把 \(a_i\) 离散化后,我们不难想到维护 dp 数组 \(g_{i,j}\) 表示 \(b_i=j\) 时的最小代价。
转移用后缀 \(\min\) 优化,复杂度 \(O(nV)\)。你发现状态数是已经不能再少了。考虑线段树维护 \(j\) 这维。
考虑转移的本质,我们先对 \(g\) 做一遍后缀 \(\min\) 之后,那么所有 \(g_{i,j}=g_{i-1,j}+f(j,a_i)\).
对于 \(j<a_i\) 的情况,区间加 \(C\) 即可。对于 \(j\ge a_i\) 的情况,区间加 \(j-a_i\)。
于是我们要支持的是区间加某数,区间把 \(i\) 加 \(b_i\),这个直接打标记即可。
现在我们需要的是如何实现做后缀 \(\min\),发现对于 \(j\ge a_i\) 的情况时已经满足递增,后缀 \(\min\) 出来不变。
只需要考虑 \(j\ge a_i\) 的跟前面做后缀 \(\min\),线段树二分出推平的位置即可。
P9531 [JOISC2022] 复兴计划
题意是:每次把边权设为 \(|w_i-x|\),求最小生成树。
如果把绝对值拆开,分别求出最小生成树呢,那么我们只需要做的是:
把边排序,每次加入一条边权为 \(0\) 的边,其他边统一加上 \(D\),再求生成树。
假设加了是 \((u, v)\),这条边必选。如果成环,我们只需要找原树一条路径上最大边删掉即可。
现在我们要合并两个生成树。而这是需要再做一遍 kruskal 的,所以上述只是一个不那么暴力的做法。
所以我们决定分治。观察到,把每条边单独考虑,一条边存在的时间是一个区间。
我们取出 \(mid\) 位置的 MST,此时不在 MST 里的边只对一侧有贡献。
此时在 MST 里面的边呢?其存在的区间跨越 \(mid\),所以这条边要传进两侧。
同时,如果一条边在 MST 里,且其传的某一侧已经被其完全覆盖,那么就可以并查集并上了。
这样的话,一个边,相当于线段树,只会拆成 \(O(\log n)\) 个区间,在这些区间里被做了一遍 kruskal。
细节的话用可撤销并查集。同时排序的话事先排好,到时候再归并。
复杂度 \(O(m\log V)\)。
P7215 [JOISC2020] 首都
我们可以发现可以求出以下关系 \(x\to y\),代表选了颜色 \(x\) 就要选 \(y\)。
我们把 \(x\) 之间路径的颜色取出来,然后向他们建边即可,这个应该可以用优化建图解决。
然后现在要求一个最小的闭合子图,考虑先缩点,然后在形成的 DAG 中找一个没有出边的最小的即可。
同时这个题还启示我们用点分治解决。
对于分治的每一层,我们令分治中心必选,然后不断拓展,最多扩展完整个子树。
如果拓展到子树外,就一定经过上一层分治中心,而上一层的答案我们已经求过,所以直接跳过即可。
所以,点分治不只是计算路径的工具,而是分治,这是我一直以来的误区。
P6845 [CEOI2019] Dynamic Diameter
首先我们有一个结论就是:两个点集的直径的四个端点中的两个是在合并之后的直径端点。
这启示我们用线段树来维护点集。一个区间代表一个点集。
现在考虑如何更新,更新一个边权,会对子树外和子树内之间的合并造成影响。
我们只需要把包含子树的区间的子集区间更新一下,其实只需从更新子树的区间往上 pushup 即可。
我们处理树上路径可以不用树剖,距离写成深度的差,然后用树状数组来维护一下子树加,单点查。
P9132 [USACO23FEB] Watching Cowflix P
我们不难对于每个 \(k\) 设出一个树形 dp,设 \(f_{u,0/1}\) 表示 \(u\) 子树,\(u\) 选/不选的最小代价。
联通块的处理的话,每合并两个连通块,就减去 \(k\) 的代价。每个 \(k\) 都要做一遍,复杂度太大。
我们还有一种想法是因为每个边加入了就不会删除,连通块只会越来越少,我们增量地维护。
一开始的时候每个关键点作为一个连通块,我们每次都合并一些距离 \(\le k\) 的连通块。
所以,连通块的个数不超过 \(\dfrac{n}{k}\) 个,那么我们根号分治。设阈值为 \(B\)。思路同 CF1039D
对于 \(\le B\) 的直接暴力,对于 \(>B\) 的因为连通块数不超过 \(\dfrac{n}{B}\),每个联通块个数都二分出其区间。
因为我们一开始想的是分治,所以我们还是分治。
若分治 \(mid\) 答案是 \(g_{mid}\),若其答案与 \(g_l,g_r\) 中某个相同,说明就下面分治整个区间都是这个取值。
需要卡常,建议把树拍成 dfs 序。