立一个 (flag):不口胡
大概是会刷 (Codeforces) 上的和各省省选题和校内模拟赛的题目
现在做题大概会多少结合数据结构,感觉如果字符串不结合数据结构确实是少点意思
Ernd
套路显然,在前面加是在 (parent) 树上跳儿子(也可能是到自己),后面是走 (SAM o DAG)
所以直接 (dp[x][i]) 表示第 (x) 点,长度为 (i) ,转移分为从自己,父亲和到拓扑上的后继
每次转移加上 (sz[tar])
显然往自己转移一定不劣,所以每次先把自己转移满,就可以往 (id[i]) 里面塞自动机上 (len=i) 的点,最后按顺序转移即可
CF319D
被打爆了,完全不会字符串了
求两个长度相同的同串复兴了一个做法:每 (len) 个上打个标记,然后前后扫,相邻的两个标记判断 (LCP,LCS)
剩下的全是暴力做就行了,如果当前串没有修改过,那么不进行重构 (hash) 呀啥的
不太知道自己想了半天的 (SAM) 在想什么
Border的四种求法
因为一定有一个 (endpos) 是 (r) ,所以这题转出来就变得清晰了许多
对应出来这个点的链,那么问题就是找到树上的 (max{endposin [l,r]},len[p]ge endpos-l+1) 的这个 (p),同时需要的是最大的 (endpos)
因为上面的点的 (endpos) 肯定比下面的大,但是 (len) 要比下面的小,所以貌似没有单调性
然后只会暴力跳父亲,所以写了 (50pts)(为啥长度不满足就不跳了不给分)
拓展上面的做法,按照上面的思路可以先倍增到想对应的位置,省去无效的查询
不过还是完全不会,貌似在线是没法支持的,所以试试离线的思路
关键结论:(Border) 可以理解成一个最大的 (i) 满足 (lcs(i,r)ge i-l+1)
然后扔到 (parent) 树上面就是两个点的 (len[lca]) 最大
那么每个询问的 (lca) 必然在 (ed[r] o root) 的链上面
所以变成了一个很好的数据结构题,学到了很多:
先对 (parent) 树链剖分,每个询问的挂到所有的重链的底部:
把所有的询问放到 (r) 向上的重链底下,最后统一遍历所有重链来对所有的答案进行更新
可能的 (lca) 只会在这条链上面,但是构成 (lca) 的另外一个前缀点就可能在链顶的子树里面
遍历重链的时候考虑维护当前点作为 (lca) ,把所有其轻子树里面的前缀点放到一个线段树里面
这个线段树下表为 (i) 的点存 (i-len(lca(ed[i],now)))
那么对于挂在这个点上的询问进行线段树上的二分即可
归总,其实这是我真正意义上第一道做的树剖或者 (DS) 题吧
倒和字符串没有太大关系了
最后:数据水得过分了,过不了小数据点的代码能过大数据(所以我就数据点分治过掉了这题)
(upd:) 其实答案统计当然是要枚举两个点,一个是在维护的 (SGT) 上的,另一个是在原来的线段数合并得到的那个上面查非完全子段的答案
所以一开始写挂了
CF 653F
基础的思路是在 (SAM) 上面 (dp),维护一个线段树,往左边转移的时候就左平移,反之就右平移
其实可以考虑维护平衡树,然后搞启发式合并,写的时候稍微奇怪一点 ,复杂度是 (Theta( nlog n)) 的
但是显然不会写,所以考虑怎么简单着做,其实本质不同那么直接钦定节点,然后如果要满足括号序列就按照括号序列式地做,先二分在这个点的区间满足的那部分
然后在 (vector) 里面二分前缀和相同的点就行了
NOI2016 优秀的拆分
设 (C_1[i]) 表示 有几个 (AA) 结尾在 (i) 点,(C_2[i]) 表示有几个 (BB) 开始于 (i) 点
答案就是 (sum_i C_1[i] imes C_2[i-1])
那么考虑如何计算出来这两个数组,其本质就是维护有多少个相同且相连的子串
上面那题的套路做就行了,设置关键点然后差分,复杂度 (Theta(Tnlog n))
不太清楚为啥多测清不空,最后全上的 (memset)
双重学了 (SA)
Codeforces1063F
观察一下发现是个最长下降的模型,所以考虑 (DP)
直接硬上 (SAM) 很不优美,所以翻转一下,变成最长上升
设 (dp[i]) 为到反转后的第 (i) 个点的最长上升序列的长度
显然存在最优的方式使得 (|t_i|le |t_{i+1}|) ,那么 (dp[i]) 也就表示了最后一个串的长度
那么求出来当前的 ([i-dp_i+1,i-1],[i-dp_i+2,i]) 的串的表示,看看是不是有可转移的点即可
现在是 (O(nlog^2n)) 的复杂度(我没写不知道能不能过掉)
不过由 (SAM) 建立的时候的写法就能观察出来 (i-len[pos]) 是单调不降的
那么加入 (dp[i]) 的时候维护一个指针
这样还是 (Theta(nlog^2n)) 的复杂度
考虑这样一个结论, (dp[i]le dp[i-1]+1),貌似是 (trivial) 的
那么就做完了
Codeforces1073G
发现每两个点开始的 (lcp) 就是后缀树上 (lca) 的 (len)
然后套上虚树就不难得到一个 (dp) 做法
每个点维护俩变量,(cnt_a,cnt_b) 然后遍历儿子的时候叠加即可
写这题的原因是我忘记虚树怎么写了,所以复习了一下:
首先思想在树中加入所有点和其 (lca)
是用一个单调栈来维护从根开始的链,按照 (dfn) 排个序,考虑增量
如果当前点是栈顶点的子树里面的点,那么直接推到栈里面
反之考虑弹栈,一直弹到栈顶和这个点的 (lca) 为栈顶,然后把这个点推进去
最后统一 (dp)
Codefores1400F
题目里面定义的区间很奇怪,对一 (8) 来说一共只有 (3) 种区间需要删掉
那么考虑把所有的非法区间扔到 (AC) 自动机里面,用它能表示子串的性质,然后在上面 (dp) 即可
这题记录一个 (AC) 自动机的错点,如果把 (1) 当成根的话,那么需要把所有的点的所有没有的儿子设成 (1)
否则转移到 (0) 号点上就错了
Luogu3041
(1A) 的时候还是很惊讶的,毕竟好久没写 (AC) 自动机 (+dp) 了
把所有的串扔到自动机里面,然后每个串统计贡献进行 (dp)
但是发现这样的话样例是过不去的,其实问题出在没法计算其子串的贡献
其实这个 (AC) 自动机搞出来的图是个 (DAG) 那么可以考虑拓扑一下,把小的计到大的上
最后统一 (dp),这步应该是显然的,我滚动数组优化了一下
Codeforces1202E
这个 ((2e5 )^2) 十分吓人,所以重点考虑怎么从这里优化
无数个题的套路,前面后面乘积,那么正反俩 (AC\_Automation) 就行了
Codeforces1037H
先对 (S) 建 (SAM) 然后把 (T) 扔到上面跑
显然的贪心就是匹配尽量多的位置然后最后补上一个最小的点
那么对于每个 (T) 上的字符跑匹配的过程中维护当前串是不是在 ([l,r]) 中出现过
这个是 (trivial) 的,因为你的名字里面有一样的做法
那么线段树合并之后枚举下一个点跑就完事了
Luogu5576
一开始的思路是建出来广义 (SAM) 然后每次询问跑所有点,判断是不是 ([l,r]) 中的点都在这个点上
然后想到离线,把询问挂到什么东西上,对于每个点线段树合并的时候维护一下
考虑 (set) 和启发式合并来规避空间的限制,然后就写了两天的 区间合并
接着得到了 (40pts) 的好成绩
最后还是学到了分治的想法:(真的想不太到)
考虑正经 (SAM) 维护 (lcp) 的题目的做法,那么考虑设当前有一个 (x)
长度 (le x) 的是短串,反之是长串
如果一个区间里面全是短(或长)串就 (++/--x) 即可
要短串中位置中间的一个作为区间的标准串建 (SAM),([mid,r]) 的求后缀,$[l,mid] $ 维护前缀的最大值
那么考虑所有跨过 (mid) 的询问直接在 ([l,r]) 中查询最大值即可
这个思路确实是开阔了视野,其实也不太难写出来233