做题记录

P7469 [NOI Online 2021 提高组] 积木小赛

考虑子序列是 \(2^n\) 级别的,子串则只有 \(n^2\),是我们可以接受的复杂度。

考虑对于一个子串能否找到一个子序列,考虑扩展的时候,如果我子串扩展一格,子序列相应的会扩展到对应的最前面的点。举例来说,如果子串后面拼上了一个 a,那么子序列就要去找最靠前的一个 a。

然后哈希维护子串内容,枚举每个起点依次扩展即可。

P4391 [BalticOI 2009] Radio Transmission 无线传输

可以从这个角度来理解:
我可以看做从头开始,有一个循环节复制好多遍,然后最后剩下一段“残缺的”,也可以看作从后面开始,有一个循环节复制,最后剩下一段“残缺的”。

这两个循环节是轮换的。

从这个角度,我们就可以发现,把两种情况都从一边砍去一个字符,当我们砍完一个循环节时,他们又是相同的了。而这就是 kmp 的 nxt 数组了。

循环节问题可以考虑 kmp 的 nxt 数组。

我们可以接着来看这道题:

[USACO03FALL] Milking Grid(数据加强版)

我这个矩阵是整个矩阵的一个循环节,那么这个行列长度都是每一行的循环节。

比如说这个矩阵是 \(a \times b\),那么意味着对于每一行,长度为 a 的前缀是循环节,对于列也是一样。

那么我们可以考虑对于行和列分别做。

但是答案会是长度的 lcm 再乘起来吗?

考虑并不是这样,因为每个行/列最短的循环节,最后的 lcm 并不一定是最小的 lcm。

比如说一个行,3 和 4 都可以是循环节,再有另一行循环节是 2。那么选 4 就比 3 要更优了。

这怎么解决呢?

分着做是不好搞的,考虑如果我把一列当成一个单位,然后对于行做循环节就好了。

对于列也做一遍,行列长度就都知道了。

P7114 [NOIP2020] 字符串匹配

首先我们肯定要将 C 单出来,然后再把 AB 绑到一块。
那么问题现在是如何求一段 \((AB)\) 是这个 \((AB)^i\) 的循环节。

考虑前两道题是广义的循环,这道题的循环是狭义的,那么定然有循环节长度是最短循环节倍数

我们可以 kmp 预处理 nxt 然后枚举前面 \((AB)^i\) 的长度,然后就很好知道是不是一个循环节了。

那知道了这个怎么做呢?

考虑我可以枚举 C 的长度,然后去看对于所有循环节,有多少 AB 划分可以使得 \(F(A) \leq F(C)\)

考虑这个东西我可以在枚举循环节的时候顺便求出来 A 的 \(F(A)\),然后就可以了。

但是这样的话我们的复杂度是 \(O(N^2)\) 怎么办呢?

这种两个部分的,我们可以考虑分开做,预处理答案。

这里我可以先枚举前面 \((AB)^i\) 的长度,然后预处理 \(F(A) \leq x (x \in [0, 26])\) 的个数,然后再去枚举 C。

复杂度低了一点,但还是过不了。

考虑我去将一个前缀分为一些循环节不好,如果我们反过来,先枚举一段前缀作为 \(AB\),然后枚举 i,看 \((AB)^i\) 是不是 S 的前缀,这样复杂度就降至调和级数。

P3065 [USACO12DEC] First! G

首先考虑建立 trie 树。

然后你发现如果一个字符串能是字典序最小的,那么必须在每一层,当前字符串的对应字符都是最大的。

这是一个很典的维护优先关系,可以考虑转化为有向图,然后通过 topu 判环判断可行性。

P4407 [JSOI2009] 电子字典

建立 trie 树,在 trie 树上 dfs。

P3538 [POI 2012] OKR-A Horrible Poem

首先,对于这类“严格”的循环节问题,我们不需要枚举所有前缀来看是否为循环节,我们只需要枚举长度的约数就可以了。

这样时间复杂度就降到了 \(O(q\sqrt(n))\)

我们可以有一个小 trick,因为我们需要的是一个最小的约数,所以不需要 \(O(\sqrt(n))\) 枚举所有约数,而可以去依次扣掉质因子,然后判断是否可行。

这样询问的复杂度就降到了 \(O(\log n)\),而对于分解质因数,我们可以通过素数筛能筛出最小质因子这个特点来进行加速

[HAOI2010] 软件安装

一眼缩点然后树上背包。

注意一些写法上的问题和一些细节:

  1. 强连通分量要写 in_stack
  2. 缩点后可能是一个森林,要连边到虚根。
  3. 如果一个环缩成一个点,也要连到虚根。
  4. 没跑 tarjan 前也要把点连到虚根。

P3854 [TJOI2008] 通讯网破坏

先点双,把割点单拎出来建成一个点,其余的建成一个点,然后就是一个树形结构。

如果 S 和 T 在一个点双内,那么肯定无法断开;
如果 M 是一个割点,同时 M 在 S 和 T 之间路径上,那么就可以断开,否则不行。

首先可以树剖,其次可以考虑树上一点在两点路径上的充要条件:

\[dis(S, M) + dis(M, T) = dis(S, T) \]

这个可以用 lca + dep 维护。

或者我们可以跑一个圆方树,其本质就是一颗连通性与原图相同的树,所以之前的操作也可以在圆方树上进行。

注意,建圆方树时,不用对根进行特判,跳栈推荐写法:

while (true) {
	int x = st.top(); st.pop();
	edge[tot].push_back(x);
	edge[x].push_back(tot);
	if (x == v) break;
}

P1407 [国家集训队] 稳定婚姻

如果一对男女不稳定,那么定然是他们在一个环上面,然后每个人轮换到下家。
同时,这个环上的边,是婚姻-初恋轮换出现的。

考虑如果是双向边的话,就没法出来边的轮换,所以我们建立单向边,同时,因为轮换的缘故,其中如果婚姻关系是男->女,那么初恋关系就要是女->男。

建出来这个图后,tarjan 判环(强连通分量),打上 belong 就行。

注意,要对每个点跑一边 tarjan。

for (int i = 1;i <= n;i++) {
	if (!dfn[i]) tarjan(i);
}

P5357 【模板】AC 自动机

别样的板子小赛。
说一些注意特征:

  1. get_fail 记得将存在的儿子加入队列:
if (!v) v = son[Fail][i];
else {
	fail[v] = son[Fail][i];
	q.push(v); // 这里
}
  1. 记得把 0 的儿子 fail 指向 0,并且记得把加边写在 while 头上:
while (q.size()) {
	int u = q.front(); q.pop();
	edge[fail[u]].push_back(u);
	......
}

P4768 [NOI2018] 归程

首先一条路径是一段坐车,剩一段步行前往。

我们就是在一次询问中,在所有能坐车到的点中,选择步行到 1 的最近的一个。

首先,我们可以想到最大生成树,这样可以保证我们到其他点路径上最小边最大。

我们可以对其建立 kruskal 重构树,这其实就是把原先存在的点作为叶子的一个二叉堆。

将边转成虚点后,两个点之间最小的边就是重构树上两点 lca 的点权。

再考虑到我们的边是越来越小的,所以一个点子树内的点权都比我大,也即子树内的点我都能走到。

我可以倍增得到我能到的最上面的点,然后剩下就是步行的部分,我找一个子树内到 1 最近的一个点即可,这个是好做的,跑一边 dij,dfs 维护子树内最小的 dis 就行。

注意细节:

倍增时,我要利用父亲的东西进行更新,所以如果我已经是根了,那么我没有父亲,就会错。需要特殊写,比如 w[0] = 1e18 等。

P3389 【模板】高斯消元法

十分暴力,用 long double 来增加精度,同时要在消第 i 个元的时候,把第 i 列上最大的一个数换到上面,可以避免系数过大。

如果发现这一列剩下的系数都是 0 了,这就是无解或者说无穷解。

因为这意味着这剩下的方程组中,完全没有这个元了。

posted @ 2025-11-11 20:21  yanbinmu  阅读(3)  评论(0)    收藏  举报