Normal Data Structure Tricks

Normal Data Structure Tricks

即 Data Structure 做题记录。

放一些比较常见的数据结构处理技巧,会一点一点补上来。

P3313 [SDOI2014]旅行

给你一个 \(10^5\) 长的序列,每个点有颜色 \(c\) 和权值 \(v\)

有修改和查询操作,修改可以为修改一个点的颜色或权值,查询一段区间内颜色为 \(c\) 的点的点权最大值以及权值和。

\(\bigstar\texttt{Trick}\):对于每个颜色开一个动态开点线段树。每次操作最多只会建立 \(\log n\) 个节点。

原题只不过讲上面的操作放在了树上,我直接莽一个树剖上去就做完了。

P2056 [ZJOI2007]捉迷藏

单点修改,维护全局最大、次大值。

\(n\le 2\times 10^5\)

一种常数超级大的想法是用 muiltiset,但是不建议尝试。

\(\bigstar\texttt{Trick}\)

  • 我们可以维护两个队,分别维护存在的与不存在的值。

    如果加入一个值,就向第一个堆中添加元素;如果删除一个值,就向第二个堆中添加元素。

  • 查询的时候如果两个堆中的元素相同,就同时 pop,最后可以留下需要的值。

原题就是这个技巧反复运用再套上氡态淀粉质啦!

XJOI 彩虹路径

一棵树上每条边可以选择一些颜色,每条边可以选择的颜色给定,定义一条路径的美观度为:路径上相邻两条边且颜色不同的边的对数。查询从 \(u\)\(v\) 路径上的美观度最大为多少。

\(n\le 10^5\)

\(\bigstar\texttt{interesting}\):发现如果一条边的颜色数量大于 \(3\),那么一定存在对于所有询问的一组最优解,这条边仅仅使用了三种颜色。

那么可以随机选择三种颜色作为这条边的备选颜色,之后倍增 dp 乱跑即可。

\(\texttt{code}\)

CF1093G Multidimensional Queries

给你 \(n\)\(k\) 维的点 \((x_1,x_2,\dots,x_k)\),定义两点间的距离为它们的曼哈顿距离 \(\sum_{i=1}^k|a_i-b_i|\)

需要维护一下两种操作:

  • 1 i a1 a2 a3...ak 表示将第 \(i\) 个点坐标改为 \((a_1,a_2,\dots,a_k)\)
  • 2 l r 表示询问编号在 \([l,r]\) 内的所有点两两间曼哈顿距离的最大值。

\(n\le 2\times 10^5,k\le 5\)

\(\bigstar\texttt{Trick}\)

  • 发现曼哈顿距离是带有绝对值的,比较难解决,考虑去掉绝对值。

    比如两个二维平面内的点 \(a,b\) 曼哈顿距离可以表示为:

    \[\begin{aligned} Dist&=\max(a_1-b_1,b_1-a_1)+\max(a_2-b_2,b_2-a_2)\\ &=\max(a_1-b_1+a_2-b_2,a_1-b_1-a_2+b_2,-a_1+b_1+a_2-b_2,-a_1+b_1-a_2+b_2) \end{aligned} \]

    发现如果我们记 \(s_{a,0}=-a_1-a_2,s_{a,1}=-a_1+a_2,s_{a,2}=a_1-a_2,s_{a,3}=a_1+a_2\),即用二进制表示所有可能的答案情况,答案其实就是:

    \[\max_{i=0}^{2^k-1}\{s_{a,i}-s_{b,i}\} \]

  • 那么只用对每个点维护 \(2^k\) 个信息即可。

回到这道题,我们用 \(32\) 棵线段树维护区间最大、最小值即可。

P4137 Rmq Problem / mex

静态,区间求 \(mex\),值域 \(\in[0,2\times 10^5]\)

\(\bigstar\texttt{Trick}\):可以用主席树表示值域,记录每一个数最后一次出现的位置,并记录区间内最后一次出现位置最靠前的位置,查询直接线段树上二分即可。

咕咕咕

P3569 [POI2014]KAR-Cards

\(n\) 张纸牌,每张正反都有一个数。每次操作交换两张牌,询问是否可以通过若干次翻转纸牌操作使 \(n\) 张纸牌上的数单调不降。

\(n\le 2\times 10^5\)

考虑在 \(\texttt{TREE}\) 中记录:

  • \(l[i(0/1)]\) ,表示左端点较小、较大的数。
  • \(r[i(0/1)]\) ,表示右端点较小、较大的数。
  • \(ans[i(0,1)][j(0/1)]\) ,表示左右端点分别为 \(i,j\) 时是否存在答案。

\(\texttt{pushup}\) 时枚举答案的左右端点、中间部分的左右端点(复杂度 \(O(16)\)

注意初始化的时候,将单个节点的 \(ans[0][0]=ans[1][1]=1\) ,而 \(ans[0][1]=ans[1][0]=0\)

咕咕咕

CF1149C Tree Generator™

\(2n-2\) 个括号组成的合法括号序列唯一地对应着一棵 \(n\) 个点的树,对应方式详见原题面。

现在有 \(q\) 次询问,每次询问在上一次询问的基础上交换两个括号(当然树的形态也随之改变),询问改变后树的直径是多少。

\(n,q\le 10^5\)

一上来就想交换括号对树形态的影响,但最终发现没有前途,不会了。。。

\(\bigstar\texttt{Hint-1}\):考虑由括号对应树的规则,其实是向下走加入一个 \(\texttt{(}\)向上走加入一个 \(\texttt{)}\),那么树上的一条链就是反复抵消后,残余的不匹配括号。

即,假设从树上链的一端 \(u\) 经过 \(lca\) 走到另一端 \(v\),一定有 \((dep_u-dep_{lca})\times \texttt{)}\)\((dep_v-dep{lca})\times \texttt{(}\) 没有被抵消,那么剩余的不匹配括号数量就是链的长度

考虑直径,将题意转化:

最长的链,就是原先的括号序列中任意子区间中去掉合法括号匹配后的剩余括号数量的最大值。

\(\bigstar\texttt{Hint-2}\):“发现”去掉合法匹配后,剩余括号只用三种可能:\(\texttt{..))))..;..((((..;..))((..}\)

按照套路,将 \(\texttt{(}\) 设为 \(+1\),将 \(\texttt{)}\) 设为 \(-1\)\(sum(l,r)\) 表示在 \([l,r]\) 中所有括号权值之和。

那么对于一段区间,答案就是:

\[\max_{k\in[l,r]}\{sum(k+1,r)-sum(l,k)\} \]

之后就可以用线段树维护啦!

记录若干个值:

  • \(sum,mx\) 表示区间和与最大值。
  • \(L_{mx},R_{mx},S_{mx}\) 表示包含 \(k\) 的贴着左端点、右端点、左右端点的最大值。
  • \(l_{mx},r_{mn}\) 表示贴着区间端点且不包含 \(k\) 的左端点最大值、右端点最小值。

P4198 楼房重建

给定长度为 \(n\) 的序列 \(\{a\}\),每次询问将一个数 \(a_i\) 修改为 \(v\)\(a_0\) 始终为 \(0\)),询问包含 \(a_0\)(最优答案必然包含 \(a_0\))的最长上升子序列的长度。

\(n\le 10^5\)

不难发现需要建出线段树,但是难点就在于两棵子树的合并。

\(\bigstar\texttt{Trick}\):这里将合并改为一组询问,假设左子树的最大值为 \(p\),合并后右子树原来 \(\text{LIS}\)被左子树挡住的那一部分是不能计入的,所以用 \(\mathcal{O(\log n)}\) 计算出右子树中被挡住了多少。即:

int query(int p,int nl,int nr,double k)
// 在 [nl,nr] 中 >= k 的楼房数量 
{
	 if(maxx[p]<=k) return 0;
	 if(nl==nr) return (Hight[nl]>k)?1:0;
	 int mid=(nl+nr)>>1;
	 if(maxx[p<<1]<k) return query(p<<1|1,mid+1,nr,k);
	 else return query(p<<1,nl,mid,k)+ans[p]-ans[p<<1];
}
merge: ans[p]=ans[p<<1]+query(p<<1|1,mid+1,nr,maxx[p<<1]);

P3987 我永远喜欢珂朵莉~

给定一个长度为 \(n\) 的非负数序列 \(a\),支持以下两种操作:

  • 1 l r x\([l,r]\) 内所有 \(x\) 的倍数 \(\div x\)
  • 2 l r 查询 \([l,r]\) 内所有数的和。

\(n,m\le 10^5;a_i,x\le 5\times 10^5\)

决定先复习一个 \(\texttt{FHQ}\) 在来写这个题。复习完了。

发现每个数最多只会有 \(\sqrt{x}\) 个因数,直接对于每个因子建立一棵平衡树,直接暴力将每个数丢到所有因子的树中,除的时候直接在所有 \(x\) 的倍数的平衡树中提取出 \([l,r]\) 这一段暴力 dfs 除掉。

那么一个数除掉 \(x\) 后可能就不应该属于其他平衡树怎么办?不用管,不就是时间复杂度上多一点了吗,每一次除的时候记得判断一下这个数是不是应该属于这个平衡树就好了。

除完之后时候记得及时重构。

P5280 [ZJOI2019]线段树

更详细、全面的讲解见神仙 Sooke 的博客

\(\bigstar\texttt{Trick}\):可以根据懒惰标记传递特点每一次操作的作用点将所有点分为 \(5\) 类点:

  • 第一类:部分包含操作区间,但是有一部分不在操作区间范围内。
  • 第二类:在操作中会被遍历到且被操作区间包含。
  • 第三类:在操作中会被遍历到但正好不在操作区间内。
  • 第四类:所有第二类节点的子节点,不会被遍历到。
  • 第五类:多有第三类节点的子节点,不会被遍历到。

这样可以对上面五类点分别用不同的方式转移,用 DP 的方式转移:(下面 \(i\) 均表示这是第 \(i\) 次操作)

\(g_{u}\) 表示所有 \(2^{i}\) 棵线段树中,从根到第 \(u\) 个线段树上的节点上一路都没有标记的线段树数量;

\(f_{u}\) 表示从下往上统计上的答案。

对于五类节点,分别转移:

  • 第一类节点:只要对它操作,原本在这个节点上的懒惰标记就会被擦除:

    \[\begin{cases} f_{u}=0+f_{u}\\ g_{u}=2^{i-1}+g_{u} \end{cases} \]

  • 第二类节点:只要对它操作,这个节点就会得到懒惰标记:

    \[\begin{cases} f_{u}=2^{i-1}+f_{u}\\ g_{u}=0+g_{u} \end{cases} \]

  • 第三类节点:需要根据从根到这个点上是否有点有懒惰标记,如果有,则这个点也有:

    \[\begin{cases} f_{u}=(2^{i-1}-g_u)+f_u\\ g_{u}=g_u+g_u \end{cases} \]

  • 第四类节点:直接从上一次的答案捞过来,但是 \(g\) 没了:

    \[\begin{cases} f_{u}=f_u+f_u\\ g_{u}=0+g_u \end{cases} \]

  • 第五类节点:不会改变,直接从上一次答案捞过来:

    \[\begin{cases} f_{u}=f_u+f_u\\ g_{u}=g_u+g_u \end{cases} \]

发现前面三种节点可以在操作的时候直接处理掉,后面两种用标记维护记录即可。

双倍经验 - P6630 [ZJOI2020] 传统艺能

P3688 [ZJOI2017] 树状数组

给定一个长度为 \(n\) 的数组 \(A\),初始都为 \(0\),接下类进行了 \(m\) 次操作:

  • 1 l r 表示在区间 \([l,r]\) 内等概率选择一个 \(x\) 并将 \([x,n]\) 颜色翻转。
  • 2 l r 询问 \(l-1\)\(r\) 两个位置颜色相同的概率。

\(n,m\le 10^5,mod=998244353\)

发现加的是一个等差数列,即对于每个数,在一次操作中都有 \(\dfrac{p-l+1}{r-l+1}\) 的概率将它的颜色翻转。

如果差分一下?可以将翻转操作先进行差分,即在 \([l,r]\) 内选择一个点将它的颜色翻转,查询区间概率积。

发现这样查询时概率是加起来的,最讨厌的就是每一段概率差分带来的 \(-1\)。这样使原本概率的二项式乘法变得非常困难。

不会了,去看题解了

\(\bigstar\texttt{Hint-1}\):还是先差分,那么修改操作就是在 \([l,r]\) 内选择一个点将颜色翻转,询问操作还是询问两个点颜色相同的概率。

\(\bigstar\texttt{Hint-2}\):如果使用线段树维护每个点修改的概率,就向上面会非常困难,无论怎样都会使得有一个操作的复杂度无法平衡。在这种情况下,考虑使用二维线段树

\((ql,qr)\) 表示 \(ql\)\(qr\) 相同的概率,可以发现每一次操作 \([l,r]\) 只会对一下三类点造成影响:

  • \(ql\in[l,r],qr>r\):那么有 \(\frac{1}{len}\) 的概率将答案修改。
  • \(ql<l,qr\in[l,r]\):那么有 \(\frac{1}{len}\) 的概率将答案修改。
  • \(ql,qr\in [l,r]\):有 \(\frac{2}{len}\) 的概率将答案修改。

那么现在变成了平面内矩形加、单点求值,套用动态开点二维线段树即可。

需要注意的是 \(l=1\) 变成了查询前缀、后缀是否相同,另外开一棵线段树即可。

\(\bigstar\texttt{Trick}\):以后遇到这种一维线段树复杂度无法平衡的题目时,不妨考虑一下转为二维线段树等。

P4183 [USACO18JAN]Cow at Large P

\(g_i\) 表示 \(i\) 到最近的叶子结点的距离。

发现假设当前根节点(初始节点)为 \(root\),点 \(u\) 内只用 \(1\) 个农民就可以的充要条件为:

\[d_{root,u}\ge g_u \land d_{root,fa_u}<g_{fa_u} \]

(后面这个条件保证如果父亲选择了,儿子就不用再重新选择一遍)

现在复杂度是 \(n^2\) 的,因为这是对点对 \((root,u)\) 的一个计数,点对问题可以让我们想用点分治解决。

但是还有一个问题,前面那个条件容易满足,后面的条件就比较难实现了?

我们发现如果一个点满足前面的条件,后面的点都会满足,考虑高明的方法消去一棵子树内多余的贡献。

\(\bigstar\texttt{Trick}\):想要如果子树中根节点满足,无论有多少个儿子(但必须都满足条件),贡献还是 \(1\),使用一下套路:假设\(u\) 为根的子树\(m\) 个儿子,发现:

\[\sum^{m}d_{i}=2m-1\\ 1=2m-\sum^{m}d_i\\ 1=\sum^{m}(2-d_i) \]

这么说,如果选择了任何的整棵子树,贡献只会是 \(1\)

那么这道题就迎刃而解了,对于 \(root\),答案为 \(\sum_{i=1}^{n}[d_{root,i}\ge g_i](2-d_i)\)

P4062 [Code+#1]Yazid 的新生舞会

给定一个长度为 \(n\) 的序列 \(\{A\}\),要求计算区间 \([l,r]\) 的数量,区间满足:区间众数的出现次数严格大于 \(\frac{r-l+1}{2}\)

\(n\le 5\times 10^5,0\le A_i\le n-1\)

求有区间绝对众数的区间的数量?发现 \(A_i\) 的值域非常小,对每一种 \(A\) 的取值求答案。

\(s_{p,i}\) 表示如果将权值为 \(p\) 的点赋为 \(1\),其余都是 \(0\) 的前缀和。

那么如果区间 \([l,r]\)\(p\) 作为绝对众数的话,一定满足:

\[2(s_{p,r}-s_{p,l-1})>r-(l-1)\\ 2s_{p,r}-r>2s_{p,l-1}-(l-1)\\ \]

\(T_i\) 表示 \(2s_{p,i}-i\),则转化为 \(T_r>T_{l-1}\),直接暴力做复杂度是 \(\mathcal{O(n^2\log n)}\) 的。

发现上面的每一段 \(T_i\) 都是随着 \(i\) 的变化相邻两位之间差为 \(1\) 的,可以对每个权值等于 \(p\) 的点只对一段等差数列操作(直观的图可以见洛谷题解)。

题意就转化为了区间加等差数列区间求和;进一步转化可以变为维护单点加、求三阶前缀和。

\(\bigstar\texttt{Trick-1}\)用树状数组维护三阶前缀和,设 \(d\)\(c\) 的前缀和,\(c\)\(b\) 的前缀和,\(b\)\(a\) 的前缀和,即 \(d\)\(a\) 的三阶前缀和,那么:

\[\begin{aligned} d_n&=\sum_{i=1}^nc_i\\ &=\sum_{i=1}^n\sum_{j=1}^ib_i\\ &=\sum_{i=1}^n\sum_{j=1}^i\sum_{k=1}^ja_i\\ &=\sum_{i=1}^n\dfrac{(n-i+2)(n-i+1)}{2}a_i\\ &=\sum_{i=1}^n\left(\dfrac{1}{2}i^2a_i-\dfrac{2n+3}{2}ia_i+\dfrac{n^2+3n+2}{2}a_i\right)\\ &=\dfrac{1}{2}\left(\sum_{i=1}^ni^2a_i\right)-\dfrac{2n+3}{2}\left(\sum_{i=1}^nia_i\right)+\dfrac{n^2+3n+2}{2}\left(\sum_{i=1}^na_i\right)\\ \end{aligned} \]

那么根据上面的式子维护 \(i^2a_i,ia_i,a_i\) 的前缀和即可。

对于这道题,加上一个倒序等差数列就等于在 \(a_l\) 加上 \(1\),在 \(a_r\) 减去 \(1\),查询的就是三阶前缀和。复杂度 \(\mathcal{O(n\log n)}\)

\(\bigstar\texttt{Trick}\)用线段树维护区间加等差数列,区间求和,可以将等差数列的首项和公差作为懒惰标记下放,及时统计区间和即可。需要注意的是如果首项不下放的错的!因为后面的几项会得不到首项的答案。

CF678F Lena and Queries

改一改式子:(下面 \(x_1<x_2\)

\[kx_1+y_1\ge kx_2+y_2\\ k(x_1-x_2)\ge y_2-y_1\\ k\le \dfrac{y_2-y_1}{x_1-x_2}\\ -k\ge \dfrac{y_1-y_2}{x_1-x_2} \]

发现答案实际上可以理解为斜率为 \(-k\) 的直线从上而下第一个切到的点。

那么本质是维护上凸壳,之后 WQS 二分。

咕了咕了

区间 GCD

维护一种数据结构,支持以下两种操作:

  • 区间加上一个数。
  • 区间查询 \(\gcd\)

\(n\le 10^5\)

\(\bigstar\texttt{Trick}\):考虑扩展辗转相除法:

\[\gcd(a,b)=\gcd(a,b-a)\\ \gcd(a_1,a_2,\dots,a_n)=\gcd(a_1,a_2-a_1,\dots,a_n-a_{n-1}) \]

那么只要对原来的序列差分,相当于单点修改,完成了这道题。

K 的妙用

维护一种数据结构,支持以下三种操作:

  • 加入一个数 \(x\)
  • 将所有 \(\ge k\) 的数减去 \(k\)
  • 查询第 \(k\) 大的数。

\(n\le 10^5,val\le 10^6\)

发现如果直接暴力将 \(\ge k\) 的数与前面的合并,复杂度可以被卡成 \(\mathcal{O(n^2)}\) 级别。

\(\bigstar\texttt{Trick}\):将每次操作分为 \([0,k),[k,2k),[2k,+\infty)\) 三段。

每次合并将前两段启发式合并,由于这两段在合并后与最后一段不会相交,可以暴力加在一起。

复杂度?发现每个点在出现在前两段的次数不超过 \(\log val\) 次,前面合并的复杂度至多为 \(\mathcal{O(n\log n\log val)}\)

posted @ 2021-11-16 18:31  EricQian06  阅读(51)  评论(0编辑  收藏  举报