Loading

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;
}
posted @ 2024-12-05 16:00  Sktn0089  阅读(146)  评论(0)    收藏  举报