点分治杂题总结
前言
由于已经点明是点分治,所以我们在文章中约定,题解只叙述:求经过当前递归到的
如有错误,欢迎指出。
其实还是比较简单一题,做多了自然就会。
首先我们先
-
表示 对应 的哪一颗子树。 -
表示以 为起点,到 的路径所能产生的路径权值。 -
表示以 为起点,到达 的路径中,最后一条的颜色。 -
表示 到 的距离。
这些应该可以通一次
然后我们按照点分治的常规思路,把
由于我们为了找到这样一条路径,需要合并两个
我们发现,这个合并似乎与
所以我们考虑把所有
首先计算不同的
显然,第一个限制,既然你
由于还有后面那个限制,所以我们考虑每枚举一种
然后看
这个时候,我们考虑在内部按照
最后取个
单层内部操作,由于排序和权值线段树,为
这个题目难点同样在合并上。
仍然,按照点分治基本的合并思路,我们需要对
-
表示 对应 的哪一颗子树。 -
表示从 开始走,走到 时,初始至少需要多少油才能走到。这个其实比较好维护,我们转化一下,就是去求你现在缺的油,和以前缺的油的最大值,维护一个前缀 即可。 -
表示从 开始走,走到 ,不管中间有没有把油耗完,到达之后所剩的油。(就不管不能为负数的条件,硬着头皮开过去还剩多少,当然可能是负数) -
表示从 开始走,能否走到 ,能为 ,否则为 。我们考虑这个怎么维护。其实可以在递归搜索的时候再传一个参数 ,表示一路下来的这条链中,每个点走回去所需要的油的最大值。假设从 开始向上走了一条路后所剩的油为 ( 可能为负数。),那么接着向下传就是 。(感觉这个虽然有点抽象,还是可以理解的。)然后,每次遍历到一个 时,就看 是否大于等于 来判断是否可以走回去。
那么有了这些东西之后,问题就简单很多了。
可以发现,如果要让一条路径合法,那就是让要合并的
那显然有一种思路是,我们维护两个数组
可以用数据结构,但是我选择排序
我们将
由于有子树内部的排序,复杂度为
科技题。
首先,我们仍然按照点分治的惯例,当然这里是求包含了
那么这里就变成了一个
这样应该可以打出一个
所以我们考虑科技。
我们发现,对于一个数
也就是说,我们的状态存在冗余。
所以我们考虑重新定义状态。
首先,对于子树内部乘积
然后,我们只需要对所有有效的状态进行转移,时间复杂度来到
然后我们考虑如何把背包的平方去掉。
由于使用了点分治,所以我们不关心过每个子树树根的连通块,只关心过点分中心(重心)的连通块,这时修改背包的定义为:包含重心的连通块延伸到子树内的种类数。
感觉这一步有点抽象啊。
然后在代码实现上,我们发现,这样每次合并的时候,只需要
感觉还是不太清楚啊,看一看部分代码。
void dfs(int x, int last)
{
siz[x] = 1;
for (int i = 1; i <= klen * 2; ++i) dp[x][i] = 0;
for (int i = 1; i <= klen; ++i) dp[x][get(i * c[x])] += dp[last][i], dp[x][get(i * c[x])] %= mod;
for (int i = c[x]; i <= klen; ++i) dp[x][klen + i / c[x]] += dp[last][klen + i], dp[x][klen + i / c[x]] %= mod;
for (int i = fst[x]; i; i = arr[i].nxt)
{
int j = arr[i].tar;
if(vis[j] || j == last) continue;
dfs(j, x);
siz[x] += siz[j];
for (int i = 1; i <= klen * 2; ++i) dp[x][i] += dp[j][i], dp[x][i] %= mod;
}
}
然后每次把
还是神仙题,点分治优化建图是个什么玩意。。
不难有一个思路,就是我们把所有的
但是显然,这么暴力建边根本就建不完。
所以接下来,我们考虑如何对这个建边进行优化。
首先,我们显然维护一个
然后,我们显然可以对
我们可以从头到尾枚举排完序后的子树,然后二分查找到最后一个满足条件的,然后与
所以我们考虑换一种思路,并把这个二分查找去掉。
我们把子树中的所有节点再开一数组个存下来,并把这个数组按照
可以发现,这是一个前缀。
我们考虑对于最开始按照距离排序的子树每个点开一个虚点
有点抽象,具体代码长这样:
stable_sort(a + 1, a + tot + 1, cmp);
stable_sort(A + 1, A + tot + 1, cmp2);
for (int i = 1; i <= tot; ++i) num[i] = ++gnow, adds(num[i], a[i]);
for (int i = 2; i <= tot; ++i) adds(num[i], num[i - 1]);
int l = 0, bj = 0;
for (int i = 1; i <= tot; ++i)
{
while(l < tot && dep[a[l + 1]] + dep[A[i]] <= c[A[i]]) ++l;
if(l) adds(A[i], num[l]);
}
然后,我们直接跑一遍缩点,并把所有含有 实点 的强连通分量当作一个黑点,否则当作一个白点。
那么问题也就转化成了,如何选择最少的黑点,使得能够通过这些黑点,到达其他所有的黑点。
其实非常简单,我们可以把所有入度为
然后,剩余的点中,入度为
点分治内部,存在排序,复杂度为
故总复杂度为
其实也蛮简单的,看到中位数最大,直接二分答案,将
于是转化为能否找到一条合法的路径,使得路径上的点权和
这不需要教了吧,直接在点分治递归内部维护每个点到
最后看这个时候是否
时间复杂度,二分,排序,点分治,
离散化之后,可以做到
做完之后,非常想骂人的一道题目。
首先先考虑如何合并两条路径使得括号匹配。(假定左括号对应点权为
接着在想,哪些路径能从上到下,哪些能从上到下。
于是就有了我们要维护的一些东西:
-
表示从 走到 这一路上的点权和。 -
表示从 走到 ,一路上点权和的最小值。(就是每到一个点取一个最小值) -
表示从 走到 ,一路上点权和的最小值。(与上面类似)
这样其实就可以完全判断了。对于
然后对于分别满足从上到下,从下到上的两个
然后我们考虑如何对两条路径计算那个什么最大嵌套。
转化一下,就是计算这条路径上,每个点前缀点权和的最大值。
于是我们继续定义两个东西:
-
表示,从 走到 的前缀点权和的最大值。 -
表示,从 走到 的前缀点权和的最大值。
于是,对于我们刚刚定义的两个
至于怎么求最大值,我们考虑直接按照
上面那
以及,记得讨论从
维护的东西是真多。。
求导神仙题。
我们定义两个点的距离为
为了方便后文的叙述,
我们仔细观察这个
于是,我们对于一个点
故,这个最小的
我们考虑去缩小这个
最开始肯定是
然后,我们从根节点的儿子看去,假设根节点的答案为
我们考虑一个变化量的问题。
如果从根节点
如果我们把
那么我们发现,如果上面那个式子在下面除以一个
而我们显然知道,对于
因为这个除下去的
而我们可以直接预处理出所有的
当然,我们每递归一次都要求一个答案,因为有可能他自己就是最大值。
但是这样的话,我们的复杂度就不对,假设递归层数为
所以我们根据点分治的思想,每次移动到一颗子树时,我们只知道答案在这颗树里面,所以我们并不关心是从那里开始的。所以就可以从这颗子树的重心开始讨论,由于判断正负的预处理只需要子树级别,就可以做到
后记
总的来说,淀粉质的题目其实分为三类。
-
通过在点分树上维护大量信息,来得到路径合并求权值,和算对最终答案贡献的目的。
-
通过点分树的,“必须包含
” 的限制,借以完成对于时间复杂度的降低,例如树上依赖背包。 -
通过点分树深度为
的限制,每次精准的找到向下递归的点,从而保证时间复杂度。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探