do_while_true

一言(ヒトコト)

几道数据结构杂题

> 查询 Ynoi 含量中......

根号含量为 0,精神状态良好!

CF1793F / CF765F / P5926 D

区间一维最近点对。\(\mathcal{O}(n\log n\log\log n+q\log\log n)\)

首先一个问题就是把有用的点对找出来,使得答案一定出现在这些点对当中。在值域上按照中位数划分做一个分治,看 \([l,mid],[mid,r]\) 之间的贡献,记 \(b_i=|a_i-mid|\),然后贡献就是 \(b_x+b_y\),如果 \(x,y\) 出现在同一边也没有关系因为求的是 \(\min\).然后按照原数组的下标排序,考虑一对有用的 \((i,k)\),必须满足 \(i<j<k\)\((i,j),(j,k)\) 都不会比其更优,那就是 \(b_i+b_k<b_i+b_j,b_j+b_k\),即 \(b_k<b_j,b_i<b_j\)

这样话,维护一个递增的单调栈,每次压入一个 \(b_k\),与其组成关键点对 \((i,k)\)\(i\) 一定是被 \(b_k\) 弹出的,以及最后一个未被弹出的那个。这样这么一次划分关键点对个数就是 \(\mathcal{O}(len)\) 个,总的个数就是 \(\mathcal{O}(n\log n)\) 个。时间复杂度也是 \(\mathcal{O}(n\log n)\)

另一个思考方向是:考虑 \(i<j<k\)\(b_i+b_j<b_i+b_k\) 那么 \((i,k)\) 无用.也就是 \(b_j<b_k,i<j\)\((i,k)\) 无用,那么对于 \(k\) 寻找支配点,就直接查一下 \(b_i<b_k\)\(i\) 中,\(k\) 的前驱和后继是啥就行(想一想,为什么这里没考虑 \(b_i>b_k\)\(i\))。实际上和上面那个找出来的是一样的。

然后就是 \(\mathcal{O}((c+q)\log\log n)\) 复杂度的 2-side \(\min\),这里 \(c\) 是点的个数其为 \(\mathcal{O}(n\log n)\)

首先对 \(r\) 作扫描线,维护后缀 \(\min\) 的单调栈。假如这样插入红点,查询一下红点的下标的前驱,看上面的值是不是大于红点的权值,如果大于就删掉。所以对下标开一棵 vEB 就可以了,然后查询就是查一下 \(l\) 在单调栈里的后继上挂的值。

卡空间 01 Trie B

一条边上可以有好多个数,也就是把只有一个儿子的节点给合并到一起。这样每次插入的时候最多只会新建两个节点了。需要位运算实现 \(\mathcal{O}(1)\) 查询二进制数的 lcp.

P7476 D

没有 2 操作怎么做/fn 线段树套堆,插入的时候在只插线段树划分出的区间里。查询的时候就是定位到的都查一下(标记永久化)。然后还要维护子树堆顶 max.

删除操作的话就直接把走到的节点需要删的就删掉然后结束递归。当询问区间并非完全包含当前节点代表区间时,还需要把误删的加回来。然后发现“把误删的加回来”最多只有两次,也就是左右两端的时候。所以每次只会增加 \(\mathcal{O}(1)\) 个段。

现在加入和删除的段数是 \(\mathcal{O}(n\log n)\) 的了,然后考虑向下递归的时候必然是要加入一个段或者删除一个段,所以向下递归的复杂度也是 \(\mathcal{O}(n\log n)\),那么总复杂度就是 \(\mathcal{O}(n\log^2 n)\)

P4786 D

\(1,-1\),使得前缀和都 \(\geq 0\),后缀和都 \(\geq 0\).暴力怎么做?前搞出前缀和,然后把如果前缀 \(\min\) 更新为负数了就贪心改掉,然后再倒过来改。需要把这个过程写成式子的形式:先花费 \(-\min\{pre_k\}\),然后考虑 \(i\) 后缀删的个数就 \((-\min\{pre_k\})-(-\min_{j<i}\{pre_j\})\)\(suf'_i=suf_i+(-\min\{pre_k\})-(-\min_{j<i}\{pre_j\})\),然后再花费 \(-\min\{suf'_i\}\)

那么总的花费就是:\(-\min\{pre_k\}-\min\{suf'_i\}=-\min\{pre_k\}-\min\{suf_i+(-\min\{pre_k\})-(-\min_{j<i}\{pre_j\})\}\)

\(=-\min_{j<i}\{suf_i+pre_j\}\)

线段树维护分治信息。

LOJ6507 B

猜到是某种势能均摊了,但是没想明白这个均摊为啥对的。

上棵线段树,记录这个区间有哪些位是已经被 区间或/与 确定好具体值的,那么再对这个区间整体覆盖这一位的时候就能得知答案了,否则继续往下递归。

考虑将每个节点的势能设置为不完全一致的位数。势能每次最多只会增加 \(\mathcal{O}(\log)\),然后花费的时间复杂度就是释放了多少势能。

P5069 D

这个题目名很美。

拉了,没看出来。注意到如果操作一个位置,那么它两边的位置就一定不会被操作了,所以就可以将答案加上这个数然后删掉它和它旁边的数。那么考虑每一个峰,答案就是这些峰的值以及中间的奇数和或者偶数和。树状数组维护一下,每次单点修改的时候讨论一下搞出答案的变化量就行。

这位更是重量级的做法是线段树维护分治信息。依然是考虑一个点选了之后它两边就一定不会被选所以可以删掉。然后考虑线段树去合并分治信息,那么就需要记录一下 \(ans_{0/1,0/1}\) 表示有没有带左端点,有没有带右端点,的答案是什么。还要记 \(l_{0/1,0/1},r_{0/1,0/1}\) 表示这种情况下左/右端点有没有选。

这个的好处是可以支持区间查询,区间加。

P5070 D

\(r\) 作扫描线,加入一个 \(x\) 的时候把 \([x-11,x+11]\) 里所有值最后一次出现的位置给提出来,从右往左扫,处理出答案的变化,那么就变成区间加单点查。

P5576 D

先考虑 \(n\) 个串的最长公共子串有啥做法,然后进行一下魔改。

首先就是把它们都查到广义 SAM 里面,然后看子树中有所有串 endpos 的节点的 len 的最大值。

现在是区间询问,那么就是包含颜色 \([l,r]\) 的节点的 len 的最大值。在 parent 树上线段树合并好像不太行,那就 set 维护连续颜色段然后启发式合并。保证 \(len\) 从大到小合并,每次合并出一个 \([l,r]\) 之后,考虑它成为那些点的答案,对于每个询问 \([L,R]\)\(R\) 挂到 \(L\) 上,通过线段树记录区间最小值就能找出能更新的询问,更新完之后再在线段树里把这个询问删掉。这样复杂度就是 polylog 的。

ICPC 2022 Jinan L B

https://codeforces.com/gym/104076/problem/L

看上去很经典,想一想发现不是很会。那就考虑一下特殊情况,发现链的情况就是区间一维最近点对(CF1793F / CF765F / P5926),那还是得从那个题搬做法来。

作扫描线然后每次找前面第一个比之前答案还有小的,最多找 log 轮那个思路不太能套,树上怎么扫描线¿

搬分治的做法,点分治,然后找支配对。支配对数是 \(\mathcal{O}(n\log n)\)

以后遇到序列上分治做的题目,就想想能不能搬到树上点分治搞,这样出题还挺好/cf

lxl 说是 codechef 某题 D

多组询问 \(\min_{l\leq x<y\leq r}\{a_x\operatorname{xor}a_y\}\). 

支配对的套路。找出数量级比较小的点对使得答案一定会在它们当中取到。

枚举一个 \(i\),考虑它作为后面那个 \(a_y\),有用的 \(a_j\) 有哪些。枚举点对 \((a_j,a_i)\) 的 LCA 的深度是 dep.考虑如果有两个位置 \(j,k\) 都合法,那么 \(LCA(a_j,a_k)\) 深度一定更深,则 \((j,k)\) 会更优。所以只有编号最大的那个 \(j\) 是有用的(当然大前提是 \(j<i\))。

换而言之,若此处有 \(k<j<i\):如果 \([l,r]\) 只包含 \(j,i\),那么就只有挑选出的支配对 \((j,i)\) 有用,如果 \([l,r]\) 包含了 \(k,j,i\),在前面一定会有更优的。故而这样只跳出 \(\mathcal{O}(n\log V)\) 个支配对,然后问题就变成了 2-side \(\min\) 问题了。

CF1446D2 D

全是重量级!\(\mathcal{O}(n\sqrt n)\) 拉了,来看看 1log 和线性的做法。

遇到最优化题目或者什么的,先想想全局最优解是什么,能否用它来简化问题(P8511 TEST_68)在这里就发现全局众数一定是答案区间的众数之一。如果一个区间的众数不是全局众数的话,考虑左端点一步步往左扩,右端点一步步往右扩,在最后全局众数会超过之前的众数,那么一定存在某个时刻它们两个相等,从而得到了更长的合法区间。

也就是如果一个区间众数不是全局众数,那么一定存在一个更大的答案,假设众数是 \(x\),枚举另一个众数 \(y\),将 \(x\) 出现位置设成 \(1\)\(y\) 出现位置设成 \(-1\),求个最长的和为 \(0\) 的区间。

思路是考虑如果暴力做是 \(\mathcal{O}(x+y)\) 的,如果 \(x>>y\) 就寄了。但是考虑此时有很多 \(x\) 是一定不会用到的。具体而言,如果从这个 \(x\) 开始往后跑,前缀和都 \(>0\),并且往前跑也都是 \(>0\),那么这个 \(x\) 一定用不到。想想怎么找出有用的 \(1\)

先看往后跑,前缀和都 \(>0\).用折线图思考这个东西,发现每次 \(-1\) 会让它前面一个 \(+1\) 合法.具体而言,对于每个 \(-1\) 匹配上它前面最近的还没有被匹配的 \(1\).那么只有匹配成功的 \(1\) 是有用的。

反过来也一样,可以做同样的事情求个交,当然也可以接着上面那个继续跑。用 set 暴力实现是 \(\mathcal{O}(n\log n)\) 的。

咋优化到线性?求交和最后统计都挺简单,问题就是对于每个 \(y\),每次和它前面一个没被匹配的 \(x\) 匹配,求出所有被匹配的 \(x\).由于 \(x\) 是固定的,预处理出每个 \(y\) 前面第一个 \(x\) 和后面第一个 \(x\)

只看 \(x\) 的话,相当于初始全 \(1\) 的序列,每次查询某个位置前面第一个 \(1\),然后将 \(1\) 置成 \(0\).但是 \(y\) 后面第一个 \(x\) 一定没有被删掉,所以其实是查询一个 \(1\) 前面第一个 \(1\),那就用链表维护一下就可以了。找完之后记录一下链表咋改变的再回退即可。

复杂度被优化到了线性。

P6105 B or D?

先求个 \(\max_{i,j\in S,i+j<C}\{i+j\}\),考虑一下桶,下标之和 \(<C\),且 \(C\) 是个常数,套路就是反转其中一个,那就令 \(a\) 为桶,\(b\) 为桶反转之后的结果,再适当右移一位,则相当于询问 \(\max_{a_i=b_j=1,i<j}\{i+C-j\}\)动态开点线段树 卡空间 只能 平衡树维护分治信息。我谔谔,不知道能不能过,懒得写。

或者考虑一下会贡献给答案的最优匹配实际上是双向的,如果要 insert \(x\),找一下 \(y\) 的最优匹配 \(z\),如果没有这个 \(z\) 或者 \(z<x\),那么 \(x,y\) 就是一个双向的最优匹配,\(x+y\) insert 到堆里面。然后可能会破坏 \(y\) 原先的最优匹配,看一下 \(z\) 的最优匹配是否是 \(y\),如果是的话再把 \(z+y\) 给删掉。

erase 也同理。把 insert 倒过来做就行。

CF453E A

没看明白题解区都是些啥。。。混乱。

先上个 set 维护颜色段,初始的那个特殊处理一下。现在问题变成了 \(\mathcal{O}(n+m)\) 次询问初始全 \(0\),区间 \([l,r]\) 在时间 \(k\) 时候的和,差分成 \([1,r]\) 的减去 \([1,l-1]\) 的。

扫描线扫序列维,在时间维上每次就是一个等差数列加和一个区间加。然后询问一个单点的和。对差分数组就是一个区间加 前缀和问题,上线段树就行。时间复杂度 \(\mathcal{O}((n+m)\log m)\)

/yiw B

从云浅那里看到的题:区间排序,区间求和。

先上个 set 维护连续段。然后每个连续段开棵值域线段树,线段树分裂 + 线段树合并,这样就能维护出每个连续段的总和。然后现在区间求和就是查询若干个连续段的和,还有一个连续段的后缀,一个连续段的前缀。

在 set 里定位一下连续段,后两个直接在值域线段树里查一下。前面那个连续段的区间和可以不写平衡树,一个连续段的值记在左端点上,用线段树维护之即可。时间复杂度 \(\mathcal{O}(n\log n)\)(忘了线段树分裂复杂度了,那就 \(n,m\) 统一成 \(n\) 算了。)

QOJ5098 D

明明应该是简单题的 ... 为什么想不出来!!

考虑一下不带修怎么做,原来是我的不带修做法做麻烦了。

考虑令 \(L_i\) 为右端点为 \(i\) 时最靠左的左端点,首先一定有 \(L\) 不降。那么询问 \([l,r]\) 时,右端点 \(i\) 就分为 \(L_i<l\)\(L_i\geq i\) 两种,二分一下分界线,前面的是 \(sum_i-sum_{l-1}\) 可以直接算,后面是 \(sum_i-sum_{L[i]-1}\) 那就是区间最值。

现在带修了,先考虑咋维护 \(L\).对于一个 \((pre_j,j)\),相当于将 \(\geq j\)\(L_i\) 均和 \(pre_j+1\) 取个 \(\max\),那每次单点修改的时候就是扔掉若干个限制再加进来若干个限制,不能删除,但考虑一下只有插入还是很好做的,那就上一发线段树分治,用线段树维护 \(L\),那后缀 \(cmax\) 也是线段树上二分一下会把哪段区间整体覆盖掉(\(L\) 不降),那么 \(sum_i-sum_{L[i]-1}\) 的区间最大值也是很好维护的了。

时间复杂度 \(\mathcal{O}((n+q)\log^2 n)\)

P6864 B

怎么场上只有四个过的??

先考虑没有撤销操作,就是每次外套一对括号或者后加一对括号,考虑合法括号子串数 \(s\) 怎么变:

  • 外套:\(s\gets s+1\)
  • 后加:令 \(ct\) 为整个串能被划分成多少个 (A) 这样的串拼接起来,然后 \(s\gets s+ct+1\)

现在又需要维护一个 \(ct\),考虑 \(ct\) 怎么变:

  • 外套:\(ct\gets 1\)
  • 后加:\(ct\gets ct+1\)

发现转移是线性的,那就写成矩阵的形式就可以了。线段树维护矩阵连乘积,这样也能支持撤销操作(也就是删除一个矩阵)。

posted @ 2023-03-21 08:01  do_while_true  阅读(66)  评论(0编辑  收藏  举报