Address
LOJ#3055
Solution
Part 1:不含 2 操作
- 我們把相鄰且相同的字母看作一段,令 \(a[i]\) 表示第 \(i\) 段的字母,\(b[i]\) 表示第 \(i\) 段的長度。
- 令 \(nxt[i]\) 表示 \(S[1\dots i]\) 的最長 \(\text{border}\) 長度。
- 如果 \(nxt[i]=j\),那么有:\(\forall k\in[1,j],a[k]=a[i-j+k],\forall k∈[2,j],b[k]=b[i-j+k],b[1]\le b[i-j+1]\)。
- 求 \(nxt\) 數組的方法和 \(\text{kmp}\) 類似。
- 如果我們已經求出了 \(nxt[1]\sim nxt[i-1]\),那么可以這樣計算第 \(i\) 段字母增加的答案:
- 設變量 \(maxl\),一開始令 \(j=nxt[i-1],maxl=0\),判斷是否 \(a[j+1]=a[i]\)。
- 如果是,那么第 \(i\) 段字母中,位置 \(maxl+1\sim 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\) 得到的。
- 但是 \(\text{kmp}\) 的時間復雜度是均攤的,也就是說,直接放在樹上做,時間復雜度是錯的。
- 考慮優化 \(\text{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\) 的所有 \(\text{border}\) 按長度排序后可分為 \(O(\log |S|)\) 段,每段是一個等差數列。(后面會證明)
- 考慮這樣做的正確性:\(j+1\) 和 \(i\) 失配了,由於 \(ls-j=j-nxt[j]\),所以 \(S[1\dots ls]\) 存在長度為 \(j-nxt[j]\) 的周期。
- 也就是說,在位置 \(j+1-per,j+1-2per......\) 還是失配的。所以直接讓 \(j\%=per\) 即可。
- \(nxt[m-1],nxt[nxt[m-1]],......\) 都是 \(m-1\) 的 \(\text{border}\)。所以跳 \(nxt\) 的過程中,每個時刻的 \(j\) 都是 \(m-1\) 的 \(\text{border}\)。
- 而上述情況中,\(ls,j,nxt[j],nxt[nxt[j]],......\) 形成了一個等差數列。
- 所以令 \(j\%=per\) 就直接跳過這個等差數列了。
- 當 \(ls-j\ne j-nxt[j]\) 時,我們可以認為是跳過了 \(ls\) 所在的等差數列。
- 因此每次都會跳過一個等差數列,跳的次數上限 \(O(\log i)\)。
- 也就是說,\(\text{kmp}\) 可以做到單次 \(O(\log n)\)。
- 總時間復雜度 \(O(n\log n)\)。
Part 3:證明結論
- 首先證明:字符串 \(S[1\dots n]\) 的所有不小於 \(\frac{n}{2}\) 的 \(\text{border}\) 長度組成一個等差數列。
- 設 \(S\) 的最小 \(\text{period}\) 長度為 \(p\),若 \(p\le \frac{n}{2}\),則存在長度 \(\ge \frac{n}{2}\) 的 \(\text{border}\)。
- 可得 \(S\) 有長度為 \(kp(1\le\ k\le \frac{n}{p})\) 的周期,也就是說 \(S\) 有長度為 \(n-kp\) 的 \(\text{border}\),\(k\) 可以取 \(1\sim \frac{n}{p}\) 中的任意一個。
- 而最小的 \(n-kp\) 肯定小於 \(\frac{n}{2}\)。
- 因為最小的 \(n-kp\) 就是 \(n\% p\),而 \(p\le \frac{n}{2}\),所以 \(n\% p<\frac{n}{2}\)。
- 因此所有 \(\ge \frac{n}{2}\) 的 \(\text{border}\) 都能表示成 \(n-kp\),證畢。
- 接下來,將 \(S[1\dots n]\) 的 \(\text{border}\) 按長度分類:\([1,1],[2,3],[4,7],\dots ,[2^{k-1},2^k-1],[2^k,n-1]\),其中 \(2^{k+1}>n\)。
- 對於兩個字符串 \(u,v\),若 \(|u|=|v|\),則記 \(P(u,v)=\left\{k:k>\frac{|u|}{2},u[1\dots k]=v[|v|-k+1\dots |v|]\right\}\)。
- 長度在 \([2^{i-1},2^i]\) 之間的 \(\text{border}\) 即 \(P(S[1\dots 2^i],S[n-2^i+1\dots n])\)。
- 長度在 \([2^k,n-1]\) 之間的 \(\text{border}\) 即 \(P(S,S)\)。
- 下面證明 \(P(u,v)\) 中的元素能組成一個等差數列:
- 取出 \(P(u,v)\) 中最大的元素 \(x\),剩下的元素都是 \(u[1\dots x]\) 的 \(\text{border}\)。
- 根據:字符串 \(S\) 的所有不小於 \(\frac{|S|}{2}\) 的 \(\text{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;
}