Address
LOJ#3055
Solution
Part 1:不含 2 操作
- 我们把相邻且相同的字母看作一段,令 a[i] 表示第 i 段的字母,b[i] 表示第 i 段的长度。
- 令 nxt[i] 表示 S[1…i] 的最长 border 长度。
- 如果 nxt[i]=j,那么有:∀k∈[1,j],a[k]=a[i−j+k],∀k∈[2,j],b[k]=b[i−j+k],b[1]≤b[i−j+1]。
- 求 nxt 数组的方法和 kmp 类似。
- 如果我们已经求出了 nxt[1]∼nxt[i−1],那么可以这样计算第 i 段字母增加的答案:
- 设变量 maxl,一开始令 j=nxt[i−1],maxl=0,判断是否 a[j+1]=a[i]。
- 如果是,那么第 i 段字母中,位置 maxl+1∼b[j+1] 的失配指针(不是 nxt 数组,是题目所求)都在第 j+1 段中。
- 接着令 maxl=max(maxl,b[j+1]),j=nxt[j],直到 j=0 或 maxl=b[i]。
- 注意特判失配指针在第 1 段的情况。
- 时间复杂度 O(n)。
Part 2:正解
- 考虑把所有操作建成一棵树。树上的每个节点都对应某个时刻的字符串。
- 对于树上的每一条边 x→y,都有边权 (a,b),表示 y 是由 x 在末尾加上 b 个字符 a 得到的。
- 但是 kmp 的时间复杂度是均摊的,也就是说,直接放在树上做,时间复杂度是错的。
- 考虑优化 kmp 跳 nxt 的过程。
- 一开始令 ls=i−1,j=nxt[i−1]。
- 如果 j+1 和 i 失配了,分情况讨论:
if (ls - j == j - nxt[j])
{
int per = j - nxt[j];
ls = j % per + per;
j %= per;
}
else ls = j, j = nxt[j];
- 给出一个结论:把字符串 S 的所有 border 按长度排序后可分为 O(log|S|) 段,每段是一个等差数列。(后面会证明)
- 考虑这样做的正确性:j+1 和 i 失配了,由于 ls−j=j−nxt[j],所以 S[1…ls] 存在长度为 j−nxt[j] 的周期。
- 也就是说,在位置 j+1−per,j+1−2per...... 还是失配的。所以直接让 j%=per 即可。
- nxt[m−1],nxt[nxt[m−1]],...... 都是 m−1 的 border。所以跳 nxt 的过程中,每个时刻的 j 都是 m−1 的 border。
- 而上述情况中,ls,j,nxt[j],nxt[nxt[j]],...... 形成了一个等差数列。
- 所以令 j%=per 就直接跳过这个等差数列了。
- 当 ls−j≠j−nxt[j] 时,我们可以认为是跳过了 ls 所在的等差数列。
- 因此每次都会跳过一个等差数列,跳的次数上限 O(logi)。
- 也就是说,kmp 可以做到单次 O(logn)。
- 总时间复杂度 O(nlogn)。
Part 3:证明结论
- 首先证明:字符串 S[1…n] 的所有不小于 n2 的 border 长度组成一个等差数列。
- 设 S 的最小 period 长度为 p,若 p≤n2,则存在长度 ≥n2 的 border。
- 可得 S 有长度为 kp(1≤ k≤np) 的周期,也就是说 S 有长度为 n−kp 的 border,k 可以取 1∼np 中的任意一个。
- 而最小的 n−kp 肯定小于 n2。
- 因为最小的 n−kp 就是 n%p,而 p≤n2,所以 n%p<n2。
- 因此所有 ≥n2 的 border 都能表示成 n−kp,证毕。
- 接下来,将 S[1…n] 的 border 按长度分类:[1,1],[2,3],[4,7],…,[2k−1,2k−1],[2k,n−1],其中 2k+1>n。
- 对于两个字符串 u,v,若 |u|=|v|,则记 P(u,v)={k:k>|u|2,u[1…k]=v[|v|−k+1…|v|]}。
- 长度在 [2i−1,2i] 之间的 border 即 P(S[1…2i],S[n−2i+1…n])。
- 长度在 [2k,n−1] 之间的 border 即 P(S,S)。
- 下面证明 P(u,v) 中的元素能组成一个等差数列:
- 取出 P(u,v) 中最大的元素 x,剩下的元素都是 u[1…x] 的 border。
- 根据:字符串 S 的所有不小于 |S|2 的 border 长度组成一个等差数列,可知 P(u,v) 中的元素能组成一个等差数列。
- 证毕。
Code
#include <bits/stdc++.h>
using namespace std;
#define ll long long
template <class t>
inline void read(t & res)
{
char ch;
while (ch = getchar(), !isdigit(ch));
res = ch ^ 48;
while (ch = getchar(), isdigit(ch))
res = res * 10 + (ch ^ 48);
}
template <class t>
inline void print(t x)
{
if (x > 9) print(x / 10);
putchar(x % 10 + 48);
}
inline char getch()
{
char ch;
while (ch = getchar(), !isalpha(ch));
return ch;
}
const int e = 2e5 + 5, mod = 998244353;
char c[e], d[e];
int adj[e], lst[e], go[e], num, cnt[e], n, ans[e], pos[e], nxt[e], m, sum[e], len[e];
int pre[e];
inline void add(int &x, int y)
{
(x += y) >= mod && (x -= mod);
}
inline void link(int x, int y, char z, int t)
{
lst[++num] = adj[x]; adj[x] = num; go[num] = y;
c[num] = z; cnt[num] = t;
}
inline int ask(int l, int r)
{
if (l > r) return 0;
return (ll)(r - l + 1) * (l + r) / 2 % mod;
}
inline void dfs(int u)
{
for (int i = adj[u]; i; i = lst[i])
{
int v = go[i], x = cnt[i];
char y = c[i];
m++; sum[m] = 0; len[m] = x; d[m] = y;
pre[m] = pre[m - 1] + x;
if (m != 1)
{
int j = nxt[m - 1], ls = m - 1;
while (j > 0 && (d[j + 1] != d[m] || len[j + 1] != len[m]))
{
if (ls - j == j - nxt[j])
{
int per = j - nxt[j];
ls = j % per + per;
j %= per;
}
else ls = j, j = nxt[j];
}
if (j > 0 || (d[j + 1] == d[m] && len[j + 1] == len[m])) nxt[m] = j + 1;
else if (d[1] == d[m] && len[1] <= len[m]) nxt[m] = 1;
else nxt[m] = 0;
j = nxt[m - 1]; ls = m - 1;
int mx = 0;
while (j > 0 && mx < len[m])
{
if (d[j + 1] == d[m] && len[j + 1] > mx)
{
add(sum[m], ask(pre[j] + mx + 1, pre[j] + min(len[j + 1], len[m])));
mx = len[j + 1];
}
if (ls - j == j - nxt[j])
{
int per = j - nxt[j];
ls = j % per + per;
j %= per;
}
else ls = j, j = nxt[j];
}
if (mx < len[m])
{
if (d[1] == d[m])
{
int mid = min(len[1], len[m]);
add(sum[m], ask(mx + 1, mid));
sum[m] = (sum[m] + (ll)(len[m] - max(mx, mid)) * len[1]) % mod;
}
}
}
else sum[1] = ask(0, len[1] - 1);
add(sum[m], sum[m - 1]);
ans[v] = sum[m];
dfs(v);
m--;
}
}
int main()
{
read(n);
int i, op, x; char y;
for (i = 1; i <= n; i++)
{
read(op); read(x);
if (op == 1)
{
if (!x)
{
pos[i] = pos[i - 1];
continue;
}
y = getch();
pos[i] = i;
int z = pos[i - 1];
link(z, i, y, x);
}
else pos[i] = pos[x];
}
dfs(0);
for (i = 1; i <= n; i++) print(ans[pos[i]]), putchar('\n');
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?