0%

21年1月 刷题记录

哇塞怎么 2021 年了,怎么我还是那么菜。qwq

这个月做题重心在 SAM、数据结构。

因为疫情被封在学校很自闭,这个月做题很少啊,文化课也开始拖后腿了。

8日

P3181 [HAOI2016]找相同字符

题目

广义SAM。

把两个串插入广义SAM里,对于两个串分别维护不同的 \(size_0,size_1\),然后在 parent 树上统计,最后答案是 \(\sum_u size_0(u)\times size_1(u)\times (len(u)-len(link(u)))\)

9日

CF666E Forensic Examination

题目

广义SAM + 线段树合并。

\(S\) 串和所有 \(T\) 串插到 SAM 里,SAM 每个节点维护一棵线段树,记录在 \(T_{1\dots n}\) 的出现次数,建出树来再线段树合并。对于每个询问,在 parent 树上倍增找到对应节点,查线段树即可。

pair 慢死了,跑了五十多秒。

P3346 [ZJOI2015]诸神眷顾的幻想乡

题目

广义SAM。

一个显然的性质:对于树上任何一个路径,一定能找到一个点,使得一该点为根时,该路径是直上直下的。

数据限制叶子很少,就对于每个叶子为根的几棵树当成 trie 树,插到一个 trie 里。

在 SAM 里离线构造,就是数本质不同子串个数的裸题了。

10日

P6292 区间本质不同子串个数

题目

SAM + LCT + 线段树。

类比静态区间数颜色数:最于每种颜色记录 \(last_i\) 表示最晚出现位置,线段树维护区间和,询问离线后,扫描线处理,每加入一个点 \(i\) 就让 \(last_{c_i}\) 贡献 \(-1\)\(i\) 位置 \(+1\)

现在同样离线询问,扫描线时在结尾加入点 \(i\),产生的子串是目前所有的后缀,我们需要让这些子串的 \(last\) 贡献 \(-1\)

先把整个串 \(S\) 建出 SAM,然后在 parent 的树上后缀 \(S[1,i]\) 对应节点的所有祖先(一条链)就是以 \(i\) 结尾的所有后缀,不能暴力跳,想到用 LCT 维护 parent 树结构。

可以发现这条链上相邻节点代表子串的右端点相同,左端点连续。那么在 LCT 的 access 操作时,对于这条链上每一段在线段树区间修改,同时标记新的 \(last\)。最后加上当前所有后缀的贡献即可。

复杂度 \(\mathcal O(n\log^2 n + q\log n)\)

SP1811 LCS - Longest Common Substring

题目

SAM。

求最长公共前缀,就是对于 \(T\) 的每一个前缀,找到其最大的后缀是 \(S\) 的子串。

那么把 \(S\) 建出 SAM,然后在 SAM 上沿着 \(T\) 走,每走一步匹配长度加一,若走不动了,就跳 \(link\),匹配长度缩小到 \(link\) 节点的 \(len\),每一步的匹配长度取最大值即可。

除去建立 SAM 的复杂度,该部分复杂度 \(\mathcal O(|T|)\)

11日

SP1812 LCS2 - Longest Common Substring II

题目

广义SAM。

和上题类似,把所有串扔进SAM里,然后我们随便找一个串来在SAM里匹配,并且只走在所有串中都出现过的节点(分别维护 \(size\))。

14日

P4218 [CTSC2010]珠宝商

题目

题解

16日

2824 [HEOI2016/TJOI2016]排序

题目

线段树分裂 + 线段树合并。

每一个区间维护一棵权值线段树,每次排序就是把边界的区间线段树分裂,然后把这些区间的线段树合并,注意正序和倒序。

另外开一个 set 维护这些区间的左端点,便于查找所在区间。

复杂度是 \(\mathcal O((n+m)\log n)\),证明见 FlashHu 的题解

P6186 [NOI Online #1 提高组] 冒泡排序

题目

树状数组。

\(f_i\) 表示 \(j<i,a_j>a_i\)\(j\) 的个数,观察冒泡排序的性质,每轮冒泡会让所有不为零的 \(f_i\) 减一。所以查询答案就是 \(\sum_{i=1}^n[f_i>k]f_i-k\)

交换操作只会改变相邻两个的 \(f\) 值,很好处理。

逆序对个数上限是 \(n^2\) 级别的,又忘了开 long long

P6478 [NOI Online #2 提高组] 游戏

题目

树形DP + 二项式反演。

\(G(i)\) 表示恰好 \(i\) 对点匹配的方案数,不好计算,就再设 \(F(i)\) 表示至少 \(i\) 对点匹配的方案数,就可以二项式反演: \[ F(k)=\sum_{i=k}^n\binom{i}{k}G(i)\Leftrightarrow G(k)=\sum_{i=k}^n(-1)^{i-k}\binom{i}{k}F(i) \] 现在考虑 \(F(i)\) 怎么算。设 \(f(u,i)\) 表示以 \(u\) 为根的子树至少 \(i\) 对点匹配的方案数,\(sum_0(u),sum_1(u)\) 表示以 \(u\) 为根的子树里白/黑点的个数,树形DP。先背包合并: \[ f_*(u,i+j)=\sum_{i=0}^{size(u)}\sum_{j=0}^{size(v)}f(u,i)\times f(v,j) \]

然后假设 \(u\) 为黑点(白点同理),选出子树里未匹配的白点匹配: \[ f_*(u,i+1)=f(u,i+1)+f(u,i)\times (sum_0(u)-i) \]

DP 之后 \(F(i)=f(1,i)\times(n-i)!\)(剩下的点任意匹配)。总复杂度 \(\mathcal O(n^2)\)

17日

CF840D Destiny

题目

主席树。

\(k\) 很小,先建立主席树,维护权值区间内的数出现次数和。差分得到区间,每次尽量走可能有答案左区间。

复杂度 \(\mathcal O(nk\log n)\),相当于 \(k\) 次单点查询。

P5459 [BJOI2016]回转寿司

题目

离散化 + 树状数组。

前缀和一下,其实就是找 \(j<i\)\(s_i-R\le s_j\le s_i-L\)\((i,j)\) 点对个数。

离散化记得把 \(s_i-L\)\(s_i-R-1\) 一块离散化,用 unordered_map 留下映射关系,然后就可以上树状数组了,复杂度 \(\mathcal O(n\log^2n)\)

CF896C Willem, Chtholly and Seniorious

题目

珂朵莉树。

就是将相邻的相同元素作为一个元素,用 set 维护,各种操作暴力。只有在有区间推平操作且数据随机的情况使用,此时复杂度接近 \(\mathcal O(n\log n)\)

P1856 [USACO5.5]矩形周长Picture

题目

扫描线。

求矩形并的周长。先纵向扫描线,同一纵坐标的线段保证先加后减,每操作一条线段增加的答案是覆盖长度变化值的绝对值。横向再做一遍就好了。

CF558E A Simple Task

题目

珂朵莉树。

比 CF896C 还简单,直接用长为 26 的数组维护。

21日

AT2062 [AGC005D] ~K Perm Counting

题目

容斥 + DP。

\(F(i)\) 为有 \(i\) 个位置不合法的方案数,容斥(反演)一下就是求 \(\sum_{i=0}^n(-1)^iF(i)\)

要求 \(F(i)\)。每个位置 \(i\)\(i-k\)\(i+k\),会影响到其它的位置。

把坐标看作一列点,值也看成一列点,点 \(i\) 和值 \(i\pm k\) 连边,可以发现这是个二分图上 \(2k\) 条链。

要选 \(i\) 个点不合法,也就是坐标点向值点连边,但是坐标点和值点都至多连一条边。

把所有链拎出来并连成一条 \(2n\) 的大链,设 \(f(i,j,0/1)\) 表示前 \(i\) 个点连了 \(j\) 条边并且 \((i-1,i)\) 这条边是否连上的方案数。

转移就很简单了: \(\begin{cases}f(i,j,0)=f(i-1,j,0)+f(i-1,j,1)\\f(i,j,1)=f(i-1,j-1,0)\end{cases}\),注意 \(i\) 为链的开头时 \(f(i,j,1)=0\)

那么 \(F(i)=(f(2n,i,0)+f(2n,i,1))\times (n-i)!\),其中 \((n-i)!\) 表示合法的点随便放。

复杂度 \(\mathcal O(n^2)\)

22日

P4097 [HEOI2013]Segment

题目

李超线段树。

板子题。就是用线段树每个节点维护包含该区间最优线段(暴露在最高折线中横坐标跨度最大),插入线段时与原最优线段对交点分类讨论,查询就用 \(\log n\) 个节点的最优线段都更新一遍。

具体见 George1123 的题解

每插入一条线段,会分到 \(\log n\) 个节点里,而每个节点都需要 \(\mathcal O(\log n)\) 的时间对所有子区间判断新线段是否为最优线段,所以单次插入复杂度 \(\mathcal O(\log^2 n)\);查询显然 \(\mathcal O(\log n)\)

23日

P4198 楼房重建

题目

分块(正解是线段树)。

显然每个点转化成斜率。分块的话每个块内维护一个单调递增的序列,序列上二分来合并每个块,复杂度 \(\mathcal O(n\sqrt{n}\log n)\) 或者 \(\mathcal O(n\sqrt{n\log n})\),实测前者更快。

线段树做法是维护区间最大值的该区间答案,每次合并两个区间就是在右区间上二分,复杂度 \(\mathcal O(n\log^2n)\)

26日

P2056 [ZJOI2007]捉迷藏

题目

括号序 + 线段树。

将树的 括号序 弄出来,然后我们知道两点在括号序上的位置 \(pos_u,pos_v\) 之间未匹配的括号数就是距离。去掉匹配的括号,剩下的就是左边若干 ) 和右边若干 (,数目记为 \(a,b\)

我们要求黑点之间最大距离,可以类似“最大子段和”用线段树维护。

两个区间合并,左区间是以黑点开头的后缀(\(a_r,b_r\)),右区间是以黑点结尾的前缀(\(a_l,b_l\)),合并就是 \(a_r+b_l+|b_l-a_r|\),也就是 \(\max(a_r+b_r-a_l+b_l,~a_r-b_r+a_l+b_l)\)

\(lmax_1\) 为前缀最大 \(a+b\)\(lmax_2\) 为前缀最大 \(b-a\)\(rmax_1\) 为前缀最大 \(a+b\)\(rmax_2\) 为前缀最大 \(a-b\),就可以维护了。

这道题还可以用动态点分治做。

28日

SP2916 GSS5 - Can you answer these queries V

题目

猫树。

其实是可以线段树做,每次询问对于区间 \([l_1,r_1]\)\([l_2,r_2]\) 是否重叠、答案区间的两个端点是否在重叠区间,分类讨论一下。

猫树讲解。为了练一下猫树,考虑把线段树换成猫树。猫树上就需要维护端点到 \(mid\) 的权值和、包含端点的最大子段和,以及两者的前缀最大值。虽然单次询问变成 \(\mathcal O(1)\) 了,可惜在这题没啥优势。

AT2370 [AGC013D] Piling Up

题目

计数DP。

设白球 \(x\) 个,则黑球 \(n-x\) 个,cc 表示两次取球颜色,手玩一下,可以发现:

  • 00\((x,n-x)\rightarrow(x-1,n-x)\rightarrow(x-1,n-x+1)\)
  • 01\((x,n-x)\rightarrow(x-1,n-x)\rightarrow(x,n-x)\)
  • 10\((x,n-x)\rightarrow(x,n-x-1)\rightarrow(x,n-x)\)
  • 11\((x,n-x)\rightarrow(x,n-x-1)\rightarrow(x+1,n-x-1)\)

\(f(i,j)\) 表示走 \(i\) 步有 \(j\) 个白球的方案数,直接 DP 发现会重,因为 \(x\) 状态序列为 1 2 32 3 4 产生的操作序列一样。

这里有个技巧,只统计经过最小值的方案。0001 操作过程中 \(x\) 值是会变小的,当 \(x=0\) 时就没法通过这两个操作转移了,所以最小值就是 \(0\)

那么设 \(f(i,j,0/1)\) 表示走 \(i\) 步有 \(j\) 个白球、是否经过 \(x=0\) 的方案数,初始状态注意 \(f(0,0,1)=1\)\(f(0,i,0)=1\)

另外注意 00 操作是不能在 \(x=0\) 处转移的。最后复杂度 \(\mathcal O(nm)\)

31日

BZOJ#2870 最长道路tree

题目

边分治。

类似点分治,找到最接近重心的边,分成两棵子树来处理,相比点分治的优势就是处理答案简单,且不需要麻烦的容斥。不过会被菊花图卡,所以需要三度化处理。

对于这道题,就是统计答案时维护距离和路径最小值,然后双指针扫两遍。

具体地最好看 代码