【题解】Solution Set - NOIP2024集训Day10 树的直径、重⼼、中⼼
【题解】Solution Set - NOIP2024集训Day10 树的直径、重⼼、中⼼
https://www.becoder.com.cn/contest/5464
最后两道题是序列ds,不是数论
求直径的板板:「CTSC2017」网络
「CF516D」Drazil and Morning Exercise
首先,我们可以换根求出所有点的 \(f\)。
然后不会了……
思考一下,一条直径提供的到底时什么。
实际上,一条直径上的点取到 \(f\) 的另一个点一定是直径的端点。
而对于一个不在直径上的点,她肯定存在一条到直径的路径然后再到直径的一个端点。(这其实就是 这篇 里面 Part2 的第一句话的结论了,就是下面这句话。
一棵树中的所有点能到的最远距离,终点一定是一条直径的一个端点。
所有不在直径上的点的 \(f\) 一定严格大于直径上的某一个点。而就是说 \(\min f_x\) 一定在直径上。(这其实就是 这篇 里面的第一句话的结论了。
然后如果这个点为根,父亲的 \(f\) 一定严格大于儿子的 \(f\)。
基于这个单调性,我们可以将所有的 \(f\) 排序,然后 two-points,并查集维护连通性,就好了。
「CF1783G」Weighed Tree Radius
https://www.luogu.com.cn/article/grtksij3
一个众所周知的结论是,如果用一条边将两棵树连接,则新直径端点一定都来源于旧的四个直径端点。实际上有一个更强的结论:在同一棵树上的两个虚树取并,新的直径端点同样来源于旧的四个直径端点。
虚树的结论不会证,题解里有讲,但是没看懂😥,先记着,有空来学。
「CTSC2017」网络
二分答案。
显然答案的两个端点一定是在直径上的。
加入的一条边跨越的距离越长,直径减少的越多,而其余的路径成为新的直径的可能性越大。对于每一个右端点,一定存在一个临界的左端点,并且一定是单调的。考虑 two-points。所以不用二分???
哦!不对!对于每一个在直径上的点延伸出的一条极长链变长的值并不是的单调的,所以不能 two-points。那还是考虑二分。
https://www.luogu.com.cn/article/11ooc2ch
唔,好像到转化题意那一步都是对的。(但是这个结论算是猜的(因为不太会证
后面的思路其实也挺像的,确实是 two-points,但是要具体的量化并转化式子之后再用。
「CSP-S2019」树的重心
考虑每个点的贡献。就是要算有多少条边分裂过后这个点能作为当前子树的重心。
对于每个点,先拿出她每一个相邻的点的子树大小的最大值 \(mx1\) 和次大值 \(mx0\)。
如果我们割掉的边是在最大值的子树内,设割掉的大小为 \(x\) 就是要 \(\max(mx1-x,mx0)\le \left\lfloor\frac {n-x}2 \right\rfloor\Rightarrow 2mx1-n\le x\le n-2mx0\)。不妨维护线段树合并维护一下当前子树内每一个子树的大小,然后区间查询就好。
如果我们割掉的边不是在最大值的子树内,设割掉的大小为 \(x\) 就是要 \(mx1\le \left\lfloor\frac {n-x}2 \right\rfloor\Rightarrow x\le n-2mx1\)。线段树合并当前节点的时候,最后合并最大的那个儿子,提前先查一下这个就行了。
如果是要拿当前 \(u\) 的父亲那棵子树的线段树的话,可以考虑先把所有的 \(siz\) 放在一棵线段树内,然后查询的时候让两棵线段树做差就好了。
好的,上面这个我自己的做法,思路是对的,式子也对了,但是似乎没法维护。😥
看题解,发现一个很聪明的想法。
把树的重心作为根,来计算答案,这样就把能割的边限制到 \(u\) 的父亲上面那棵子树内。
只用在根节点的时候讨论最大值/次大值的同时也便于维护。
https://www.luogu.com.cn/article/p9dqpc9y
为决定这个维护还是挺妙的。
pyt 告诉我还有一种做法是:
树的重心一定是在重链上面,直接二分出一个合法的区间。
「CF566C」Logistical Questions
之前做过。
求导是因为可能不能比出来那边更好,而求导之后斜率是单增的,一定能比出来。
「CF997E」Good Subsegments
判断一个区间是否是好的,等价于区间最大值 - 区间最小值 = 区间长度。
考虑枚举最大值(没用(本来自己一开还想了不少,但是跟正解好像没什么关系,而且刚好又不小心删掉了😥
https://www.luogu.com.cn/article/s4ctbwi1
考虑从小到大枚举每一个右端点 \(i\),维护每一个合法的左端点 \(j\)。
具体地,我们在线段树上维护每个点到 \(i\) 的最大值/最小值,以及 \(mx-mi-(i-j)\) 的最小值及其个数(因为这个值一定是非负数,所以当这个式子的最小值等于 0 的时候,这个区间的全部 \(j\) 都是合法的。
这样一来,我们就可以处理每一个右端点固定左端点任意的合法区间个数。
但是题目要求我们求子区间个数。
所以我们要用一个类似扫描线的东西,我们枚举的右端点 \(i\),走到一个询问区间的左端点的时候就开始当前
考虑把这棵线段树可持久化。但是很难做那个类似前缀/差分的东西,而且空间两只 log 有点劣。
其实就是把之前所有的枚举到大于等于当前询问的左端的右端点都算到当前的答案里面。
考虑线段树上多加一个懒标 \(times\) 表示这个节点对当前的 \(i\) 造成贡献的次数。
实际上就是每次将每个线段树节点的答案计算一次并记录到自己的节点上。
实现:
-
线段树每个点 \(i\) 初值赋为 \(i\),保证右端点在枚举到 \(i\) 之前都是恒正的。
-
每次 \(times\)
push_up
的时候,需要判儿子的 \(mi\) 是不是等于自己(实际上就是判是不是和根节点相同(而不是判是不是等于 \(0\)首先肯定每次对线段树上修改完了之后,根节点的值一定是 \(0\) 因为存在右端点自己长度为 \(1\) 的子区间。
但是在修改的过程中,有可能把根节点的值改成不是 \(0\),所以应该是判是不是和根节点相等而不是是否等于 \(0\)。
「BalticOI 2024」Wall
考虑每一位对答案的贡献。
形式化的,一种方案的答案是:
\(\min\)