Runs 学习笔记
Runs
这里主要谈个人理解,几乎没有数学记号。前置知识:Lyndon 串
-
定义:对于字符串 \(s_{1\dots n}\),定义 run 为一个三元组 \((l, r, p)\) 满足 \(r - l + 1\ge 2p\) 且 \(s_{l\dots r}\) 存在周期 \(p\),并且 \(s_{l\dots r}\) 是极长的,即 \(s_{l - 1} \not = s_{l - 1 + p}\) 且 \(s_{r + 1} \not = s_{r + 1 - p}\)。
-
run 的指数:三元组 \((l, r, p)\) 的 run 指数为 \(\frac {r - l + 1} p\)。
先抛出 Runs 定理吧,不会证明。
- The "Runs" Theorem:
记 \(\rho(n)\) 和 \(\sigma(n)\) 为长度为 \(n\) 的字符串所包含的 run 的最大数量,以及所有 run 的指数总和最大值。
则有 \(\rho(n)\le n - 1, \ \sigma(n)\le 3n - 3\)。
模板问题很简单,给你一个字符串,让你求出所有 run。
- Lemma:
记 \(suf(i)\) 为以第 \(i\) 个位置开头的后缀,\(L_i = \max\{j | s_{i\dots j - 1} \in \mathcal L\}\),其中 \(\mathcal L\) 为 Lyndon 串集合。
则有 \(\min\{j | i < j \land suf(i) > suf(j)\} = L_i\)。
Proof
由于 \(\forall i < j < L_i\) 都有 \(s_{i\dots L_i - 1} < s_{j\dots L_i - 1}\),可得 \(suf(i) < suf(j)\),因此 \(\min\{j | i < j \land suf(i) > suf(j)\} \ge L_i\)。
即证 \(suf(L_i) < suf(i)\)。反证法,假设 \(suf(L_i) > suf(i)\)。
设 \(x\) 为 \(suf(i)\) 和 \(suf(L_i)\) 的最长公共后缀的长度,可得 \(s_{i + x} < s_{L_i + x}\)。
进而可得 \(\forall 0\le k\le x\) 都有 \(suf(L_i + k) > suf(i + k) > suf(i)\),所以 \(s_{i\dots L_i + x} \in \mathcal L\),这与 \(L_i\) 的极大性矛盾。
证毕。
- Lyndon Root:满足 \((l, r, p)\) 内长度为 \(p\) 且字典序最小的子串,显然一个 run 中至少存在一个 Lyndon Root。
考虑通过枚举 Lyndon Root 来寻找 run,先假定我们寻找的 run 的满足 \(s_{r + 1} < s_{r + 1 - p}\)。
枚举 Lyndon Root 的起始位置 \(i\),我们可以证明 \(s_{i\dots L_i - 1}\) 正是需要枚举的 Lyndon Root,记 \(p = L_i - i\),该子串有以下性质:
-
其内部不存在周期。反证法,若存在周期 \(q(1\le q\le p - 1)\),那么 \(s_{i + q\dots L_i}\) 是 \(s_{i\dots L_i}\) 的前缀,这与 \(s_{i\dots L_i}\in \mathcal L\) 矛盾。这保证了我们找到的 run 的周期 \(p\) 的最小性。
-
同时该子串也是自身的最小表示。反证法,对于一个 Lyndon 串 \(t_{1\dots m}\),若存在位置 \(x(2\le x\le m)\) 满足 \(t_{x\dots m} t_{1\dots x - 1} < t\),而由 Lyndon 串的定义得 \(t_{x\dots m} > t\),显然矛盾。
-
取 \(l = i - \text{lcs}(i - 1, L_i - 1), \ r = L_i - 1 + \text{lcp}(i, L_i)\),由于 \(suf(L_i) < suf(i)\),所以满足 \(s_{r + 1} < s_{r + 1 - p}\)。
然后这个可以证明包含了所有的 run,将所有字符大小关系倒过来再做一次即可。
说一下做法,求 \(L_i\) 可以单调栈,所以我们需要快速查询两个后缀的大小关系,以及求 \(l, r\) 时需要快速求解任意两个前缀的 LCS 以及任意两个后缀的 LCP。
可以 SA + ST表,或者二分哈希。
点击查看代码
#include <bits/stdc++.h>
namespace Initial {
#define ll int
#define ull unsigned long long
#define fi first
#define se second
#define mkp make_pair
#define pir pair <ll, ll>
#define pb emplace_back
#define i128 __int128
using namespace std;
const ll maxn = 1e6 + 10, inf = 1e9, mod = 1e9 + 7;
ll power(ll a, ll b = mod - 2) {
ll s = 1;
while(b) {
if(b & 1) s = 1ll * s * a %mod;
a = 1ll * a * a %mod, b >>= 1;
} return s;
}
template <class T>
const ll pls(const T x, const T y) { return x + y >= mod? x + y - mod : x + y; }
template <class T>
void add(T &x, const T y) { x = x + y >= mod? x + y - mod : x + y; }
template <class T>
void chkmax(T &x, const T y) { x = x < y? y : x; }
template <class T>
void chkmin(T &x, const T y) { x = x > y? y : x; }
} using namespace Initial;
namespace Read {
char buf[1 << 22], *p1, *p2;
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, (1 << 22) - 10, stdin), p1 == p2)? EOF : *p1++)
template <class T>
void rd(T &x) {
char ch; bool neg = 0;
while(!isdigit(ch = getchar()))
if(ch == '-') neg = 1;
x = ch - '0';
while(isdigit(ch = getchar()))
x = (x << 1) + (x << 3) + ch - '0';
if(neg) x = -x;
}
} using Read::rd;
char str[maxn]; ll n;
namespace Hash {
const ll b = 131;
ull pw[maxn], h[maxn];
void init() {
pw[0] = 1;
for(ll i = 1; i <= n; i++)
pw[i] = pw[i - 1] * b, h[i] = h[i - 1] * b + str[i];
}
ull gethsh(ll l, ll r) { return h[r] - h[l - 1] * pw[r - l + 1]; }
} using Hash::gethsh;
ll getlcp(ll x, ll y) {
ll lo = 0, hi = n - max(x, y) + 1;
while(lo <= hi) {
ll mid = lo + hi >> 1;
if(gethsh(x, x + mid - 1) == gethsh(y, y + mid - 1))
lo = mid + 1;
else hi = mid - 1;
} return hi;
}
ll getlcs(ll x, ll y) {
ll lo = 0, hi = min(x, y);
while(lo <= hi) {
ll mid = lo + hi >> 1;
if(gethsh(x - mid + 1, x) == gethsh(y - mid + 1, y))
lo = mid + 1;
else hi = mid - 1;
} return hi;
}
bool cmp(ll x, ll y) {
ll c = getlcp(x, y);
if(c == n - x + 1) return true;
if(c == n - y + 1) return false;
return str[x + c] < str[y + c];
}
struct run {
ll l, r, p;
run(ll L = 0, ll R = 0, ll P = 0) { l = L, r = R, p = P; }
}; vector <run> res;
const bool operator < (const run a, const run b) {
return a.l ^ b.l? a.l < b.l : a.p < b.p;
}
const bool operator == (const run a, const run b) {
return a.l == b.l && a.r == b.r && a.p == b.p;
}
void add(ll l, ll r) {
ll L = l - getlcs(l - 1, r), R = r + getlcp(l, r + 1);
if(2 * (r - l + 1) <= R - L + 1) res.pb(run(L, R, r - l + 1));
} ll stk[maxn], top;
void getRuns() {
stk[top = 0] = n + 1;
for(ll i = n; i; i--) {
while(top && cmp(i, stk[top])) --top;
add(i, stk[top] - 1), stk[++top] = i;
}
}
int main() {
scanf("%s", str + 1), n = strlen(str + 1);
Hash::init(), getRuns();
for(ll i = 1; i <= n; i++) str[i] = 'z' - str[i] + 'a';
getRuns();
sort(res.begin(), res.end());
res.erase(unique(res.begin(), res.end()), res.end());
printf("%d\n", (ll) res.size());
for(run t: res) printf("%d %d %d\n", t.l, t.r, t.p);
return 0;
}
本原平方串
- 定义:最小周期恰好为 \(\frac {|s|} 2\) 的字符串 \(s\) 称为本原平方串。
例如 \(\mathtt{abcabc}\) 是,而 \(\mathtt{abababab, abcd}\) 不是。
- 性质:每个位置不同的本原平方串,恰好在一个 run 内出现。
首先一定会出现,若不存在则可以找到一个新的 run。
对于恰好出现一次,考虑反证,若存在两个 run 包含同一个本原平方串,那么可以通过这个周期 \(p\) 拓展出更大的 run,矛盾。
所以我们只需要求出所有的 run,对于三元组 \((l, r, p)\),其包含的本原平方串长度为 \(2p\),包含的平方串长度分别为 \(2p, 4p, 6p, \dots\)。
位置不同的本原平方串个数为 \(\mathcal O(n\log n)\)。
本质不同的本原平方串个数为 \(\mathcal O(n)\)。
不会证明。
[ZJOI2020]字符串
考虑枚举每个 run,枚举平方串长度 \(p' = 2p, 4p, 6p, \dots\),不难发现 \((l, l + p' - 1), (l + 1, l + p'), \dots, (r - p' + 1, r)\) 形成了一条斜线。还要减掉重复的贡献 \((l, l + p'), (l + 1, l + p' + 1), \dots, (r - p', r)\),也形成了一条斜线。根据理论,所有 run 的指数之和是 \(\mathcal O(n)\)。
但是不同的 run 可能存在相同的平方串,枚举每种长度 \(p'\),以及可能的 \(p\) 个本质不同的长度为 \(p'\) 的平方串,设上一个区间为 \((l_0, r_0)\),这一次为 \((l_1,r_1)\),减掉贡献 \((l_0, r_1)\),使用 unordered_map 维护。这部分不超过位置不同的本原平方串数目,即 \(\mathcal O(n\log n)\)。
单调加可以转化为两个斜线相减,所以现在我们只需要支持加上所有斜线,然后矩形求和,可以分类讨论 + 二维数点,时间复杂度 \(\mathcal O(n\log ^ 2 n)\)。
点击查看代码
#include <bits/stdc++.h>
namespace Initial {
#define ll long long
#define ull unsigned long long
#define fi first
#define se second
#define mkp make_pair
#define pir pair <ll, ll>
#define pb emplace_back
#define i128 __int128
using namespace std;
const ll maxn = 2e5 + 10, inf = 1e18, mod = 1e9 + 7;
ll power(ll a, ll b = mod - 2) {
ll s = 1;
while(b) {
if(b & 1) s = 1ll * s * a %mod;
a = 1ll * a * a %mod, b >>= 1;
} return s;
}
template <class T>
const ll pls(const T x, const T y) { return x + y >= mod? x + y - mod : x + y; }
template <class T>
void add(T &x, const T y) { x = x + y >= mod? x + y - mod : x + y; }
template <class T>
void chkmax(T &x, const T y) { x = x < y? y : x; }
template <class T>
void chkmin(T &x, const T y) { x = x > y? y : x; }
} using namespace Initial;
namespace Read {
char buf[1 << 22], *p1, *p2;
// #define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, (1 << 22) - 10, stdin), p1 == p2)? EOF : *p1++)
template <class T>
void rd(T &x) {
char ch; bool neg = 0;
while(!isdigit(ch = getchar()))
if(ch == '-') neg = 1;
x = ch - '0';
while(isdigit(ch = getchar()))
x = (x << 1) + (x << 3) + ch - '0';
if(neg) x = -x;
}
} using Read::rd;
ll n, m, ans[maxn << 1], qL[maxn << 1], qR[maxn << 1], id[maxn << 1];
char str[maxn];
namespace Runs {
const ll fB = 2e9;
struct Hash{
ll h[maxn], pw[maxn], mod;
void init(ll b, ll m) {
pw[0] = 1, mod = m;
for(ll i = 1; i <= n; i++) {
pw[i] = pw[i - 1] * b %mod;
h[i] = (h[i - 1] * b + str[i]) %mod;
}
}
ll gethsh(ll l, ll r) {
return (h[r] - h[l - 1] * pw[r - l + 1] %mod + mod) %mod;
}
} h1, h2;
ll gethsh(ll l, ll r) { return fB * h1.gethsh(l, r) + h2.gethsh(l, r); }
ll lcp(ll x, ll y) {
ll lo = 1, hi = n - max(x, y) + 1;
while(lo <= hi) {
ll mid = lo + hi >> 1;
if(gethsh(x, x + mid - 1) == gethsh(y, y + mid - 1))
lo = mid + 1;
else hi = mid - 1;
} return hi;
}
ll lcs(ll x, ll y) {
ll lo = 1, hi = min(x, y);
while(lo <= hi) {
ll mid = lo + hi >> 1;
if(gethsh(x - mid + 1, x) == gethsh(y - mid + 1, y))
lo = mid + 1;
else hi = mid - 1;
} return hi;
}
bool cmp(ll x, ll y) {
ll c = lcp(x, y);
if(max(x, y) + c - 1 == n) return x + c > n;
return str[x + c] < str[y + c];
} ll stk[maxn], top;
struct Run {
ll l, r, p;
Run(ll L = 0, ll R = 0, ll P = 0) { l = L, r = R, p = P; }
}; vector <Run> runs;
const bool operator < (const Run a, const Run b) {
return a.l ^ b.l? a.l < b.l : a.p < b.p;
}
const bool operator == (const Run a, const Run b) {
return a.l == b.l && a.r == b.r && a.p == b.p;
}
void addrun(ll l, ll r) {
ll L = l - lcs(l - 1, r), R = r + lcp(l, r + 1);
if(R - L + 1 >= 2 * (r - l + 1)) runs.pb(Run(L, R, r - l + 1));
}
void getRuns() {
stk[top = 0] = n + 1;
for(ll i = n; i; i--) {
while(top && cmp(i, stk[top])) --top;
addrun(i, stk[top] - 1), stk[++top] = i;
}
}
void GetRuns() {
h1.init(131, 1e9 + 7), h2.init(37, 998244353);
getRuns();
for(ll i = 1; i <= n; i++) str[i] = 'a' + 'z' - str[i];
getRuns(), sort(runs.begin(), runs.end());
runs.erase(unique(runs.begin(), runs.end()), runs.end());
// for(Run t: runs)
// printf("%lld %lld %lld\n", t.l, t.r, t.p);
}
} using namespace Runs;
unordered_map <ll, ll> mp; ll ss;
struct Data { ll x, p, w; }; vector <Data> mdf;
void f_add(ll l, ll r, ll w, ll p = 0) {
if(!p) p = r - l + 1; --p;
if(p >= r - l + 1) return;
if(r > p) mdf.pb((Data) {r, p, w});
if(l > 1) mdf.pb((Data) {l + p - 1, p, -w});
}
pir tree[maxn];
const void operator += (pir &A, const pir B) {
A.fi += B.fi, A.se += B.se;
}
void tr_add(ll x, pir v) {
for(; x <= n; x += x & -x) tree[x] += v;
}
pir tr_ask(ll x) {
pir v(0, 0);
for(; x; x -= x & -x) v += tree[x];
return v;
}
int main() {
rd(n), rd(m), scanf("%s", str + 1);
GetRuns();
for(Run t: runs) {
ll l = t.l, r = t.r, p = t.p;
for(ll c = (p << 1); c <= r - l + 1; c += p << 1) {
for(ll st = l; st + c - 1 <= r && st - p < l; st++) {
ll hsh = gethsh(st, st + c - 1);
if(mp.count(hsh)) f_add(mp[hsh], st + c - 1, -1);
mp[hsh] = (r - (st + c - 1)) / p * p + st;
}
f_add(l, r, 1, c);
f_add(l, r, -1, p + c);
}
}
for(ll i = 1; i <= m; i++) {
rd(qL[i + m]), rd(qR[id[i + m] = i + m]);
qL[id[i] = i] = qR[i] = qR[i + m], --qL[i + m];
}
sort(id + 1, id + 1 + 2 * m, [](ll x, ll y) {
return qR[x] - qL[x] < qR[y] - qL[y];
});
sort(mdf.begin(), mdf.end(), [](const Data a, const Data b) {
return a.p < b.p;
});
for(ll o = 1, i, j = 0, c = 0; o <= 2 * m; o++) {
i = id[o];
while(j < mdf.size() && mdf[j].p < qR[i] - qL[i])
tr_add(mdf[j].x - mdf[j].p, mkp(mdf[j].w,
(mdf[j].x - mdf[j].p) * mdf[j].w)), c += mdf[j++].w;
pir tmp = tr_ask(qL[i]);
ans[i] = tmp.se + (c - tmp.fi) * qL[i];
} memset(tree, 0, sizeof tree);
for(ll o = 2 * m, i, j = (ll) mdf.size() - 1, s = 0, c = 0; o; o--) {
i = id[o];
while(~j && mdf[j].p >= qR[i] - qL[i])
tr_add(mdf[j].x, mkp(mdf[j].w, mdf[j].x * mdf[j].w)),
s += mdf[j].p * mdf[j].w, c += mdf[j--].w;
pir tmp = tr_ask(qR[i]);
ans[i] += tmp.se + (c - tmp.fi) * qR[i] - s;
}
for(ll i = 1; i <= m; i++) printf("%lld\n", ans[i] - ans[i + m]);
return 0;
}
【集训队作业2018】串串划分
动态规划,设 \(f_i\) 表示 \(s_{1\dots i}\) 的划分方案数。先只考虑第一个条件,我们需要从非循环串处转移过来,考虑用 \(\sum\limits_{j = 0} ^ {i - 1} f_j\) 减去循环串处的方案数。一个循环串一定恰好出现在一个 run 中,设这个 run 为 \((l, r, p)\),则循环串长度为 \(2p, 3p, 4p, \dots\)。
在每一个 run 中同时求 \(p\) 个前缀和即可,不难发现该数量为 \(r - l + 2 - 2p\) 即为位置不同本原平方串数量,为 \(\mathcal O(n\log n)\) 的。
加入第二个条件,考虑容斥。由于方案中保证了每个划分段不是循环串,所以只需考虑连续几个划分段都为一个 run 的长度为 \(p\) 的子串的情况。
发现同样是前缀和,类似维护。
点击查看代码
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
namespace Initial {
#define ll int
#define ull unsigned long long
#define fi first
#define se second
#define mkp make_pair
#define pir pair <ll, ll>
#define pb emplace_back
#define i128 __int128
using namespace std;
const ll maxn = 2e5 + 10, inf = 1e9, mod = 998244353;
ll power(ll a, ll b = mod - 2) {
ll s = 1;
while(b) {
if(b & 1) s = 1ll * s * a %mod;
a = 1ll * a * a %mod, b >>= 1;
} return s;
}
template <class T>
const ll pls(const T x, const T y) { return x + y >= mod? x + y - mod : x + y; }
template <class T>
void add(T &x, const T y) { x = x + y >= mod? x + y - mod : x + y; }
template <class T>
void chkmax(T &x, const T y) { x = x < y? y : x; }
template <class T>
void chkmin(T &x, const T y) { x = x > y? y : x; }
} using namespace Initial;
namespace Read {
char buf[1 << 22], *p1, *p2;
// #define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, (1 << 22) - 10, stdin), p1 == p2)? EOF : *p1++)
template <class T>
void rd(T &x) {
char ch; bool neg = 0;
while(!isdigit(ch = getchar()))
if(ch == '-') neg = 1;
x = ch - '0';
while(isdigit(ch = getchar()))
x = (x << 1) + (x << 3) + ch - '0';
if(neg) x = -x;
}
} using Read::rd;
ll n, dp[maxn];
char str[maxn];
namespace Runs {
struct Hash{
ull h[maxn], pw[maxn], mod;
void init(ll b, ll m) {
pw[0] = 1, mod = m;
for(ll i = 1; i <= n; i++) {
pw[i] = pw[i - 1] * b;
h[i] = (h[i - 1] * b + str[i]);
}
}
ll gethsh(ll l, ll r) {
return (h[r] - 1ll * h[l - 1] * pw[r - l + 1]);
}
} h1, h2;
pir gethsh(ll l, ll r) { return mkp(h1.gethsh(l, r), h2.gethsh(l, r)); }
const bool operator == (const pir a, const pir b) { return a.fi == b.fi && a.se == b.se; }
ll lcp(ll x, ll y) {
ll lo = 1, hi = n - max(x, y) + 1;
while(lo <= hi) {
ll mid = lo + hi >> 1;
if(gethsh(x, x + mid - 1) == gethsh(y, y + mid - 1))
lo = mid + 1;
else hi = mid - 1;
} return hi;
}
ll lcs(ll x, ll y) {
ll lo = 1, hi = min(x, y);
while(lo <= hi) {
ll mid = lo + hi >> 1;
if(gethsh(x - mid + 1, x) == gethsh(y - mid + 1, y))
lo = mid + 1;
else hi = mid - 1;
} return hi;
}
bool cmp(ll x, ll y) {
ll c = lcp(x, y);
if(max(x, y) + c - 1 == n) return x + c > n;
return str[x + c] < str[y + c];
} ll stk[maxn], top;
struct Run {
ll l, r, p;
Run(ll L = 0, ll R = 0, ll P = 0) { l = L, r = R, p = P; }
}; vector <Run> runs;
const bool operator < (const Run a, const Run b) {
return a.l ^ b.l? a.l < b.l : a.p < b.p;
}
const bool operator == (const Run a, const Run b) {
return a.l == b.l && a.r == b.r && a.p == b.p;
}
void addrun(ll l, ll r) {
ll L = l - lcs(l - 1, r), R = r + lcp(l, r + 1);
if(R - L + 1 >= 2 * (r - l + 1)) runs.pb(Run(L, R, r - l + 1));
}
void getRuns() {
stk[top = 0] = n + 1;
for(ll i = n; i; i--) {
while(top && cmp(i, stk[top])) --top;
addrun(i, stk[top] - 1), stk[++top] = i;
}
}
void GetRuns() {
h1.init(131, 1e9 + 7), h2.init(37, 998244353);
getRuns();
for(ll i = 1; i <= n; i++) str[i] = 'a' + 'z' - str[i];
getRuns(), sort(runs.begin(), runs.end());
runs.erase(unique(runs.begin(), runs.end()), runs.end());
}
} using namespace Runs;
ll m, sum, st[maxn]; vector <ll> vec[maxn];
ll *pre1[maxn], *pre2[maxn];
int main() {
scanf("%s", str + 1), n = strlen(str + 1);
GetRuns();
for(ll i = 0; i < runs.size(); i++) {
Run t = runs[i];
for(ll j = st[i] = t.l + 2 * t.p - 1; j <= t.r; j++)
vec[j].pb(i);
pre1[i] = new ll[t.r - st[i] + 1], pre2[i] = new ll[t.r - st[i] + 1];
} dp[0] = sum = 1;
for(ll i = 1; i <= n; i++) {
dp[i] = sum;
for(ll j: vec[i]) {
Run t = runs[j]; ll x = i - st[j];
pre1[j][x] = dp[i - 2 * t.p];
if(x >= t.p) add(pre1[j][x], pre1[j][x - t.p]);
add(dp[i], mod - pre1[j][x]);
pre2[j][x] = mod - dp[i - 2 * t.p];
if(x >= t.p) add(pre2[j][x], mod - pre2[j][x - t.p]);
add(dp[i], pre2[j][x]);
} add(sum, dp[i]);
} printf("%d\n", dp[n]);
return 0;
}

浙公网安备 33010602011771号