8.5
今天跑到C班了,表示十分快乐
上午
后缀数组
我居然有些理解了!!
感谢C班!!
后缀数组的构造
sa[i]:第i小的后缀的编号。
rank[i]:编号为i的后缀的排名(从小到大)。
height[i]:第i小的后缀和第i - 1 小的后缀的最长公共前缀,即LCP(suffix[sa[i]],suffix[sa[i-1]])
倍增,想要求出每个位置往后2k个字符,这些字符串的排名。
由长为2k的子串的排名可以求出长为2k+1的子串的排名
倍增O(logn)层,每层快速排序的话就是O(nlog2n),基数排序的话就是O(nlogn)。
任意两个后缀的最长公共前缀
设为后缀x和后缀y,其中rank[x]<rank[y]。
答案就是min(rank[x],rank[y])height[i]
如果已经求出了height,用ST表就可以O(nlogn)预处理,O(1)回答两个后缀的最长公共前缀。
借个图
如何求 height 数组
性质: height[rank[i]] ≥ height[rank[i − 1]] − 1
如果 height[rank[i − 1]] = 0 显然成立,否则在 i − 1 这个后缀与它前驱的 LCP 的基础上去掉第一个字符,就找到了一个后缀与 i 这个后缀的 LCP≥ height[rank[i − 1]] − 1。 进而有height[rank[i]] ≥height[rank[i − 1]] − 1。
申明此图来自博主
基数排序
开权值范围个桶,作个前缀和求出每个数的排名,然后直接把每个数放到该放的位置上去。
稳定排序,相等的元素就是按原顺序排的。
我是木得感情的截图机器人
模板题
非常模板
code
#include <bits/stdc++.h>
#define ll long long
#define lowbit(x) x & (-x)
#define memset(x) memset(x, 0, sizeof(x))
#define N 500010
#define sc(x) scanf("%d", &x)
#define pr(x) printf("%d\n", x)
using namespace std;
char s[N];
int n, p;
int sa[N], cnt[N], rank[N], ran[N], tmp[N], h[N];
void buildSA()
{
p = 0;
for (int i = 1; i <= n; i++)
p = max(p, (int)s[i]);
for (int i = 1; i <= n; i++)
cnt[s[i]]++;
for (int i = 1; i <= p; i++)
cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; i--)
sa[cnt[s[i]]--] = i;
rank[sa[1]] = p = 1;
for (int i = 2; i <= n; i++)
rank[sa[i]] = (s[sa[i]] == s[sa[i - 1]] ? p : ++p);
for (int k = 1; k <= n; k <<= 1)
{
memset(cnt);
for (int i = 1; i <= n; i++)
cnt[rank[i + k]]++;
for (int i = 1; i <= p; i++)
cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; i--)
tmp[cnt[rank[i + k]]--] = i;
memset(cnt);
for (int i = 1; i <= n; i++)
cnt[rank[i]]++;
for (int i = 1; i <= p; i++)
cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; i--)
sa[cnt[rank[tmp[i]]]--] = tmp[i];
ran[sa[1]] = p = 1;
for (int i = 2; i <= n; i++)
ran[sa[i]] = (rank[sa[i]] == rank[sa[i - 1]] && rank[sa[i] + k] == rank[sa[i - 1] + k] ? p : ++p);
for (int i = 1; i <= n; i++)
rank[i] = ran[i];
}
for (int i = 1, j = 0; i <= n; i++)
{
if (rank[i] == 1) continue;
while (s[i + j] == s[sa[rank[i] - 1] + j])
{
j++;
}
h[rank[i]] = j;
if (j) j--;
}
}
int main()
{
cin >> s + 1;
n = strlen(s + 1);
buildSA();
for (int i = 1; i <= n; i++)
printf("%d ", sa[i]);
printf("\n");
for (int i = 2; i <= n; i++)
printf("%d ", h[i]);
printf("\n");
return 0;
}
Trie 上后缀数组
这还不会
Trie 上一个后缀定义为一个点的到根路径。
求后缀数组直接类比序列情形,倍增。
如果把中间过程的 rank 数组都记录下来就可以求两个节点到根路径的 LCP 了。
基础应用
参考论文:罗穗骞:《后缀数组——处理字符串的有力工具》
例题(来自罗穗骞:《后缀数组——处理字符串的有力工具》)
下午
做题
A - Musical Theme
先得到差分序列,再用后缀数组找出最长公共前缀,中间利用了二分答案
code
#include<set>
#include<map>
#include<cmath>
#include<queue>
#include<bitset>
#include<string>
#include<cstdio>
#include<cctype>
#include<cassert>
#include<cstdlib>
#include<cstring>
#include<sstream>
#include<iostream>
#include<algorithm>
#define ll long long
#define lowbit(x) x & (-x)
#define memset(x) memset(x, 0, sizeof(x))
#define N 500010
#define sc(x) scanf("%d", &x)
#define pr(x) printf("%d\n", x)
using namespace std;
int a[N], s[N], sa[N], cnt[N], rank[N], ran[N], tmp[N], h[N];
int n, p;
void buildSA()
{
p = 0;
for (int i = 1; i <= n; i++)
p = max(p, s[i]);
for (int i = 1; i <= n; i++)
cnt[s[i]]++;
for (int i = 1; i <= p; i++)
cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; i--)
sa[cnt[s[i]]--] = i;
rank[sa[1]] = p = 1;
for (int i = 2; i <= n; i++)
rank[sa[i]] = (s[sa[i]] == s[sa[i - 1]] ? p : ++p);
for (int k = 1; k <= n; k <<= 1)
{
memset(cnt);
for (int i = 1; i <= n; i++)
cnt[rank[i + k]]++;
for (int i = 1; i <= p; i++)
cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; i--)
tmp[cnt[rank[i + k]]--] = i;
memset(cnt);
for (int i = 1; i <= n; i++)
cnt[rank[i]]++;
for (int i = 1; i <= p; i++)
cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; i--)
sa[cnt[rank[tmp[i]]]--] = tmp[i];
ran[sa[1]] = p = 1;
for (int i = 2; i <= n; i++)
ran[sa[i]] = (rank[sa[i]] == rank[sa[i - 1]] && rank[sa[i] + k] == rank[sa[i - 1] + k] ? p : ++p);
for (int i = 1; i <= n; i++)
rank[i] = ran[i];
}
for (int i = 1, j = 0; i <= n; i++)
{
if (rank[i] == 1) continue;
while (s[i + j] == s[sa[rank[i] - 1] + j])
{
j++;
}
h[rank[i]] = j;
if (j) j--;
}
}
bool check(int x)
{
int maxx, minn;
for (int i = 1; i <= n; i++)
{
if (h[i] < x) maxx = minn = sa[i];
else
{
maxx = max(maxx, sa[i]);
minn = min(minn, sa[i]);
if (maxx - minn > x) return 1;
}
}
return 0;
}
int main()
{
while (1)
{
scanf("%d", &n);
if (!n) break;
memset(cnt);
memset(sa);
memset(rank);
memset(ran);
memset(h);
memset(tmp);
memset(a);
memset(s);
for (int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
s[i] = a[i] - a[i - 1] + 100;
}
buildSA();
int l = 0, r = n, mid, ans = 0;
while (l < r)
{
mid = (l + r) / 2;
if (check(mid)) l = mid + 1, ans = mid;
else r = mid;
}
if (ans >= 4) printf("%d\n", ans + 1);
else pr(0);
}
return 0;
}
总结
在C班学了后,对后缀数组理解了一些,但还没有完全弄清楚,例题也没做多少,可以看看博客之类的
后缀数组的模板要打熟,会用到二分答案之类的
百度百科:后缀数组
翻到的比较好的博客: