数据结构 泛做
享受一个人在机房里面调 \(DS\) 的感受
感觉有些处理方式是很妙的
长链剖分
组合
设 \(f[x][i]\) 为叉的下长减上长为 \(i\) 的方案,\(g[x][i]\) 为距离其长度为 \(i\) 的点数
直接转移是 \(\Theta(n^2)\) 的,使用长链剖分进行优化
长链剖分将重儿子的定义改为最长链的儿子,一个性质是树上所有长链的总和为 \(n\)
转移的时候每个长链的儿子显然可以继承父亲的信息,另一个令人耳目一新的操作是使用数组分配下标的方式:先开一个长度为 \(2n\) 的数组,每次 \(f,g\) 直接分配,儿子的数组 f[son[x]]=f[x]+1,g[son[x]]=g[x]+1
剩下的都是 \(dp\) 的转移了:
For(j,0,len[t]-1){
ans+=g[t][j]*f[x][j+1]+f[t][j+1]*g[x][j];
if(j) f[x][j-1]+=f[t][j];
}
For(j,0,len[t]-1) f[x][j+1]+=g[t][j]*g[x][j+1],g[x][j+1]+=g[t][j];
Link Cut Tree
THUWC2017 在美妙的数学王国中畅游
想到不联通显然 \(LCT\),那么变成抄式子,给了泰勒展开那么维护若干项即可:
考虑 \(sin(x)\) 的泰勒展开:
那么复合函数求导数得到:
所以套上去就得到了 \(sin(ax+b)\) 的泰勒展开的式子,也就是乘上 \(a\) 的若干次方
考虑 \(e^{ax+b}\) 的展开
设 \(f(x)=e^x,g(x)=a(x)+b\),还是能得到 \((f(g(x)))'\)
多求导就是多乘 \(a\)
那么套上 \(Taylor\) 展开的式子,维护每次的系数和,剩下的都是「模板」\(LCT\) 了
所以这题的收获是 \(Taylor\) 展开科技:\(f(x)=\sum \frac{f'(x)\times x^i}{i!}\)
ZJOI2016 大森林
考虑题设可以把它们都离线之后统一处理,那么 \(trivial\) 地用扫描线处理操作
也就是每棵树先长出来最终形态再回答询问
对于生长一个节点,直接差分,最后统一做即可
而对于更换生长点,考虑建立若干虚点表示操作,但是没有点权(真正生长出来的点有点权,为了求 \(dis\) )
具体而言,一开始求最终方案的时候
修改 \(l,r\) 的生长点,先对存在 \(x\) 号点的区间取交集
之后让新点和原来的生长点连边,再和新的生长点连边
在最后的统一处理中把虚点切掉/连上即可
平衡树
【WC2016】鏖战表达式
水高分容易 \(AC\) 难
用可持久化 \(fhq\ treap\) 做其实十分显然,所以 \(80\ pts\) 是 \(trivial\) 的
在 \(\texttt{mathew99}\) 的博客里面给出了卡掉的构造:先来一堆 \(ord=1\),再来一堆 \(ord=2\),一次类推,这样这个算法的复杂度到了 \(k\log n\)
而正解用了随机化:对于 \(fhq \ treap\) 的合并,这样写:
if((ord[l]==ord[r]&&rand()%(sz[x]+sz[y])<sz[x])||ord[l]<ord[r]) balabala...
else balabala....
这样单次复杂度期望 \(O(k+\log n)\)
证明?Itst说有2019/2018的论文,然而论文网址废掉了
记录两个 \(\texttt{fhq treap}\) 的手残的地方:
\((1)\) \(\texttt{split}\) 第二个的时候记得是 \(\texttt{split(x,size,x,z)}\)
\((2)\) 可持久化的时候 \(merge\) 写的可持久化后的点
Codeforces702F T-shirt
一开始得到一个二分的做法,但是发现这个没有单调性,所以就放弃了
换一个角度,\(fhq \ treap\) 对每个人的获得量和当前剩余的钱数进行维护
对于每个货物,二分得到买得起它的子树,打钱数减少和答案增加的标记
对于权值减少后的重复,也就是 \(cost\le v\le 2cost\) 的 \(v\) 暴力插入平衡树里面,剩下的因为肯定还是权值大的部分,直接合并
每次插入会导致 \(v\) 减少一半,所以复杂度是合法的
这个标记下方要注意,尤其是最后输出答案的时候要遍历全树下放标记
【Hdu6087】Rikka with Sequence
维护原始的平衡树来对付 \(3\) 操作,每次拆出来对应的区间合并就行
对于 \(2\) 操作,考虑 \(k<r-l+1\) 的时候这个区间是会被不断复制的
那么把区间分裂出来,然后倍增出来每段的树,最后统一合并区间即可
这里还有一个 \(trick\) 是定期重构,具体而言: if(tot<M-N/3) rebuild();
cdq 分治
Luogu4655
一万年没写过斜率优化了,所以重学了斜率优化
\(dp\) 式子是 \(trivial\) 的,如下:
如果 \(j>k\) 同时
那么 \(j\) 的决策比 \(k\) 优
但是 \(h_i\) 不保证满足任何条件,所以我们并不知道这要维护个啥凸包
所以 \(cdq\) 分治来解决动态凸包的离线维护:
按照 \(cdq\) 分治优化 \(dp\) 的原理,先处理左侧的 \(f\) 区间内左边对右边的贡献,最后处理右边的答案
对于当前分治区间 \([l,r]\),先按照 \(id\) 排序,处理左边的答案,回溯之后把左边的凸包建出来,这样满足转移都是有效的
然后把右边的点按照 \(x\) 排序进行转移即可
复杂度 \(O(n\log n)\) 或者用 \(sort\) 就多个 \(\log\)
具体实现的时候也可以先把所有点的 \(w\) 都扔到 \(f_1\) 上然后每次转移可以方便一点
Luogu4849
然后就写了个 \(cdq\) 套 \(cdq\) 的模板题……
由 \(cdq\) 的原理,考虑 \(sort\) 掉若干维之后剩下的维中左对右的贡献
处理低维 \(cdq\) 的时候我们本质上是把左侧的 \(a\) 只转移给右侧的 \(a\)
那么先做一次 \(cdq\) 把 \(b\) 也同样操作一下,标记上是 \(L/R\)
所以 \(a\in L,b\in L\) 的才可以添加进入 \(BIT\) 同时 \(a\in R,b\in R\) 的才可以查询
再套上个 \(cdq\) 支持多维偏序即可
代码其实 \(trivial\) ,想法确实有意思
Luogu6007
设 \(f_{x,y}\) 表示到 \((x,y)\) 的最小步数
转移是 \(trivial\) 的,\(f_{x,y}=\min(min((f_{x-1,y},f_{x,y-1})+1,f_{p,q}[q\le x,q\le y])\)
既然是个 \(cdq\) 的题目,那么总该有点 \(cdq\) 的求最大值的样子
所以考虑转化成最多节省了多少步,则答案为 \(2m-f_{m,m}\)
每次支持查询 \((1,1)\to (x_1,y_1)\) 的最大的 \(f\) 即可
稍微转化一点的 \(cdq\)
目前不会 \(BIT\) 的 \(\Theta(n\log n)\) 的做法
虚树
HNOI2014 世界树
得到一个朴素的 \(dp\),设 \(f_x\) 表示 \(f_x\) 的归属,那么 \(bfs\) 可以得到
时间复杂度 \(O(nq)\)
但是这样虚树的数据范围就没用了,发现其实可以分割链的贡献
不难进行若干次 \(dfs/dp\) 求出来每个虚树上的点的归属,设为 \(bel[x]\)
对于一条链 \((u,v)\) 分如下的情况讨论
\((1)\) 如果 \(bel[u]=bel[v]\) 那么直接找到 \(u\) 在 \(v\) 方向上的儿子 \(s\),贡献是 \(sz[s]-sz[v]\)
\((2)\) \(bel[u]\neq bel[v]\) 二分端点,\(sz\) 的贡献并不难得出来
\((3)\) 虚树上的点不在虚树上的子树的贡献,显然应该贡献给 \(bel[u]\)
时间复杂度 \(O(nlog^xn)\)
HEOI2014 大工程
建虚树,然后 \(dp\) 考虑每个虚树上的链的贡献即可
注意:每个链的经过次数是 \(sz[x]\times (m-sz[x])\)
点分治
分岔路口
显然先跳然后走会比较优秀,那么这个做法对两点的距离没有要求,而且答案一样
所以计算一下这个分界然后判断即可,写出在对于一个范围:\(E=\frac{n+Sum}{num}\)
\(num\) 表示在范围内的点的个数,\(Sum\) 表示这些点的距离和,这部分可以使用点分治/树
\(Id\) 函数和 \(F(x)=\frac{n+Sum}{Num}\) 都是连续的,交点必然只有一个,所以可以使用二分求交点
实现的时候每次让 \(ans\) 对二分得到的期望取 \(min\) 保证正确性
luogu4115
本来想套 \(ZJOI2007\) 捉迷藏的线段树维护直径的做法,但是交了几次发现不行
其实也就是因为那个题的边权都是正的,这题从下往上传递信息的时候会挂掉
那么还是去想点分治
每个点存到点分树夫亲的距离 \(d[x]\) 和 所有点分树儿子的最大值
具体维护时,用第一个堆来维护第二个堆(这里看了题解)
当然需要支持可删除堆来代替 \(set\) ,那么我又复习了如何写可删除堆
想了半天才懂,懂了就好说了
CF990G
大力点分治,每次直接 \(O(n^2)\) 合并子树信息即可
不太清楚为啥能过,但是过掉了
看题解说 \(gcd\) 有收敛性?
Luogu2664
如果当前点 \(x\) 的颜色是从根到之的第一次出现的,那么它对于根的其它子树的答案有 \(sz[u]\) 的贡献
那么我们现在仅考虑如何求得跨过 \(root\) 的答案
考虑记在当前分治重心的总权值和为 \(sum\) ,那么其对于 \(ans[x]\) 的贡献要减掉 \(x\) 子树里面的点的贡献
接着考虑在 \(x\to root\) 的路径上的点,设个数为 \(num\) 不难得到贡献应该是其它子树里面的和:\((sz[root]-sz[x]) \times num\)
大体思路并不复杂,魔鬼永远在细节:分治重心的处理和子树内部颜色的重复
需要想清楚,想清楚了并不复杂
归总,写很多个 \(dfs\) 就行了
注意!!点分治找重心的时候记得清空 \(f[x]\)
Codeforces150E Freezing with Style
首先把中位数都离散化下来,然后点分治的过程中进行二分
以上都是从 \(\texttt{WC2010重建计划}\) 中套出来的 \(\texttt{trivial}\) 套路
显然是考虑对于每个分治重心维护跨过其的最大的两个链进行合并
中间套个单调队列维护两个信息就行了
貌似单调队列写了一万年,为啥都快省选了单调队列这种东西还要调……
Luogu3714
点分治,接着考虑如何合并两个子树的信息
按照上个题的思路,两个不同颜色的合并需要减掉对应的代价
具体而言,对于每个扫出来的子树,合并长度为 \(i\) 的路径信息就查 \(\texttt{[L-i,R-i]}\) 最大值
所以搞俩线段树,颜色不一样的时候线段树合并就好了
动态 DP
如何求带修改的最大独立集?
把转移分成两个,即 \(f[i][0/1]\) 表示(不)选择当前点的答案
同时 \(g[i][0/1]\) (不)考虑重儿子当前点的答案
这东西可以写成一个矩阵乘法的形式
那么每次被更改的 \(f,g\) 数组构成一条链,对矩阵开线段树维护即可
卡常方式貌似是用 \(LCT\) 或者循环展开
有效的是每个重链开线段树来降低 \(query\) 的复杂度