数据结构 泛做

享受一个人在机房里面调 \(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)\) 的泰勒展开:

\[f(x)=\sin(x),f'(x)=\cos(x),f''(x)=-\sin(x),f'''(x)=\cos(x),f''''(x)=\sin(x) \]

那么复合函数求导数得到:

\[(f(g(x)))'=f'(g(x))\times g'(x) \]

所以套上去就得到了 \(sin(ax+b)\) 的泰勒展开的式子,也就是乘上 \(a\) 的若干次方

考虑 \(e^{ax+b}\) 的展开

\(f(x)=e^x,g(x)=a(x)+b\),还是能得到 \((f(g(x)))'\)

\[(f(g(x)))'=f'(g(x))\times g'(x)=af(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\) 的,如下:

\[f_{i}=(\min_{j<i} f_j-s_{j}+(h_i-h_j)^2)+s_{i-1} \]

\[f_i=(\min_{j<i} f_j-s_{j}+h_j^2-2h_ih_j)+s_{i-1}+h_i^2 \]

如果 \(j>k\) 同时

\[\frac{f_j-f_k+s_{k+1}-s_{j}+h_j^2-h_k^2}{h_j-h_k}\le 2h_i \]

那么 \(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\) 的复杂度

posted @ 2021-01-14 18:45  yspm  阅读(120)  评论(0编辑  收藏  举报