闲话 22.12.22
闲话
想怎么开场呢
然后突然想到一个直切正题的引言:
”我们闲话少说……“
那就上今天的推歌吧!
Feedback/Artery by AVTechNO! & 镜音双子
有些人比较听不惯早期(?)虚拟歌姬的这种电子感
当然现在也有以 Synthesizer V AI 为例的神经网络优化歌声合成技术
但说实在的 这种机械感的确在很多情况下不可或缺
完 不会组织语言
后缀数组附题
对 来补点后缀数组的题。
给定 \(n\) 个长度不超过 \(m\) 字符串,字符范围为 \([0, 2000]\)。需要找出这些字符串的最长的差分相同公共子串。
\(n, m \le 1000\)。
差分相同,因此作差分后转化为相同子串,答案 \(+1\) 即可。
然后需要找到这些字符串的最长公共子串。
使用 SA 的话可以先把所有字符串串起来(中间插入不存在的字符),然后跑出来 \(\text{height}\) 数组。然后二分答案转化为判定。众所周知 \(\text{height}\) 数组表示一个位置队医的后缀和其前一位对应后缀的 \(\text{LCP}\) 长度,因此如果存在 \(n\) 个起点彼此不同的起点,满足 \(\text{height} \ge mid\),则 \(mid\) 是可以取到的。
二分答案即可。
使用 SAM 的话就先拿一个串建自动机,随后将其他串扔上去匹配就行。
SA code
#include <bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for (register int i = (a), i##_ = (b) + 1; i < i##_; ++i)
#define pre(i,a,b) for (register int i = (a), i##_ = (b) - 1; i > i##_; --i)
const int N = 3e6 + 10, inf = 1e9 + 7;
int n, t1, str[N], id[N], mlc, l, r, mn, mx, siz;
vector<int> raw[N];
int sa[N], rk[N], height[N];
void get_sa(int ch[], int n, int sa[], int rk[]) {
int m = siz;
static int cnt[N], rk2[N], key[N], id[N];
memset(cnt+1, 0, m << 2);
rep(i,1,n) rk[i] = ch[i], cnt[rk[i]]++;
rep(i,1,m) cnt[i] += cnt[i-1];
pre(i,n,1) sa[cnt[rk[i]] --] = i;
for (int w = 1; ; w <<= 1) {
int p = 0;
for (register int i = n; i > n - w; -- i) id[++p] = i;
rep(i,1,n) if (sa[i] > w) id[++p] = sa[i] - w;
memset(cnt + 1, 0, m << 2);
rep(i,1,n) key[i] = rk[id[i]], cnt[key[i]]++;
rep(i,1,m) cnt[i] += cnt[i-1];
pre(i,n,1) sa[cnt[key[i]] --] = id[i];
memcpy(rk2+1, rk+1, n << 2);
p = 0;
rep(i,1,n) rk[sa[i]] = (
rk2[sa[i]] == rk2[sa[i-1]] and
rk2[sa[i] + w] == rk2[sa[i-1] + w]
) ? p : ++p;
if (p == n) { rep(i,1,n) sa[rk[i]] = i; break; }
m = p;
}
}
void get_height(int s[], int n) {
for (int i = 1, k = 0; i <= n; i++) {
if (rk[i] == 0) continue;
if (k) k--;
while (s[i + k] == s[sa[rk[i] - 1] + k]) k++;
height[rk[i]] = k;
}
}
bool vis[N];
vector <int> stk;
bool check(int val) {
for (auto x : stk) vis[x] = 0; stk.clear();
rep(i,1,mlc) {
if (height[i] < val) { for (auto x : stk) vis[x] = 0; stk.clear(); }
if (!vis[id[sa[i]]]) {
vis[id[sa[i]]] = 1;
stk.push_back(id[sa[i]]);
if (stk.size() >= n) return true;
}
} return false;
}
signed main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n;
l = mx = 0, r = mn = inf;
rep(i,1,n) {
cin >> t1; raw[i].resize(t1);
rep(j,0,raw[i].size() - 1) cin >> raw[i][j], (j > 0) && (mx = max(mx, raw[i][j] - raw[i][j-1]), mn = min(mn, raw[i][j] - raw[i][j-1]), true);
r = min(r, (int)raw[i].size() - 1);
}
rep(i,1,n) {
rep(j,1,raw[i].size() - 1) str[++mlc] = raw[i][j] - raw[i][j-1], id[mlc] = i;
str[++mlc] = ++mx;
}
rep(i,1,mlc) str[i] = str[i] - mn + 1, siz = max(siz, str[i]);
get_sa(str, mlc, sa, rk); get_height(str, mlc);
int l = 0, r = siz + 1, mid, ans;
while (l <= r) {
mid = l + r >> 1;
if (check(mid)) ans = mid, l = mid + 1;
else r = mid - 1;
} cout << ans + 1 << endl;
}
题面长,不粘。
我写的是 SA + 莫队。跑得挺快?因为常数巨小冲到了最优解。
首先把母串(喵星人的名字)拼起来,中间放个彼此不同的大数字,然后跑 SA。给到一个点名串,我们就可以在 \(\text{sa}\) 数组上通过不断二分的方式定位一个区间,满足这些区间里任意元素对应的后缀和点名串的 \(\text{LCP}\) 是这个点名串本身。这个区间里所有串都能做贡献,这样我们就将问题转化成了颜色个数和颜色贡献区间数了。
颜色个数可以直接做莫队。颜色贡献的区间数可以差分。当一个颜色可以贡献时加入后缀长度,不能贡献时减去即可。
总时间复杂度 \(O(SA(n) + q(\log q + \sqrt n))\)。
code
#include <bits/stdc++.h>
using namespace std;
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
const int N = 2e5 + 5;
const int inf = 0x3f3f3f3f;
int n, siz, m, q, l, r, t1, t2, t3, a[N], mlc, id[N], tmp = 1e4;
struct queries {
int l, r, bel, id;
bool operator < (const queries & b) const {
if (bel == b.bel) return r < b.r;
return l < b.l;
}
} qy[N];
int sa[N], rk[N];
void getsa(int ch[], int n, int sa[] = sa, int rk[] = rk) {
int m = tmp;
static int cntmx[N], rk2[N], key[N], id[N];
memset(cntmx+1, 0, m * sizeof(*cntmx));
rep(i,1,n) rk[i] = ch[i], cntmx[rk[i]]++;
rep(i,1,m) cntmx[i] += cntmx[i-1];
pre(i,n,1) sa[cntmx[rk[i]] --] = i;
for (int w = 1; ; w <<= 1) {
int p = 0;
for (register int i = n; i > n - w; -- i) id[++p] = i;
rep(i,1,n) if (sa[i] > w) id[++p] = sa[i] - w;
memset(cntmx + 1, 0, m * sizeof(*cntmx));
rep(i,1,n) key[i] = rk[id[i]], cntmx[key[i]]++;
rep(i,1,m) cntmx[i] += cntmx[i-1];
pre(i,n,1) sa[cntmx[key[i]] --] = id[i];
memcpy(rk2+1, rk+1, n * sizeof(*rk2));
p = 0;
rep(i,1,n) rk[sa[i]] = (
rk2[sa[i]] == rk2[sa[i-1]] and
rk2[sa[i] + w] == rk2[sa[i-1] + w]
) ? p : ++p;
if (p == n) { rep(i,1,n) sa[rk[i]] = i; break; }
m = p;
}
}
int cnt[N], ans1[N], ans2[N], ret;
#define add(x, idt) \
++ cnt[id[x]];\
if (cnt[id[x]] == 1) ret ++, ans2[id[x]] += q - idt + 1;
#define del(x, idt)\
-- cnt[id[x]];\
if (cnt[id[x]] == 0) ret --, ans2[id[x]] -= q - idt + 1;
signed main() {
cin >> n >> m;
rep(i,1,n) rep(j,0,1) {
cin >> t1;
rep(k,1,t1) cin >> a[++ mlc], id[mlc] = i;
a[++ mlc] = ++ tmp;
}
siz = sqrt(n);
getsa(a, mlc);
rep(i,1,m) {
cin >> t1;
int L, R, j;
for (L = 1, j = 1, R = mlc; j <= t1; ++ j) {
cin >> t2;
int l = L, r = R, mid;
while (l <= r) {
mid = l + r >> 1;
if (a[sa[mid] + j - 1] < t2) l = mid + 1;
else r = mid - 1;
}
t3 = l; l = L, r = R;
while (l <= r) {
mid = l + r >> 1;
if (a[sa[mid] + j - 1] <= t2) l = mid + 1;
else r = mid - 1;
}
L = t3, R = r;
}
if (L <= R) qy[++ q] = { L, R, (L - 1) / siz + 1, i };
}
sort(qy + 1, qy + 1 + q);
for (int i = 1, l = 1, r = 0; i <= q; ++ i) {
while (qy[i].l < l) {
-- l;
add(sa[l], i);
}
while (r < qy[i].r) {
++ r;
add(sa[r], i);
}
while (qy[i].l > l) {
del(sa[l], i);
++ l;
}
while (r > qy[i].r) {
del(sa[r], i);
-- r;
} ans1[qy[i].id] = ret;
}
rep(i,1,m) cout << ans1[i] << '\n';
rep(i,1,n) cout << ans2[i] << ' ';
}
给定一个长度为 \(n\) 的字符串 \(S\),令 \(T_i\) 表示它从第 \(i\) 个字符开始的后缀。求
\[\sum_{1\le i<j\le n}\text{len}(T_i)+\text{len}(T_j)-2\times\text{LCP}(T_i,T_j) \]其中,\(\text{len}(a)\) 表示字符串 \(a\) 的长度,\(\text{LCP}(a,b)\) 表示字符串 \(a\) 和字符串 \(b\) 的最长公共前缀。
\(2\le n\le 500000\),且 \(S\) 中均为小写字母。
原式其实就等于
因此考虑 \(\text{LCP}\) 如何计算。首先求 SA 得到 \(\text{height}\),可以发现 \(\text{LCP}(T_i,T_j) = \min\limits_{k=i+1}^j \text{height}(k)\)。
套路确定左端点后线段树维护各个右端点答案之和。然后区间取最小值区间求和就行了。
为什么你放着好写的 SAM 做法和快的单调栈做法不写反而写这一坨东西呢?
主要是想复习吉司机线段树了。真不会写了啊啊啊
而且真不用动脑子
code
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
const int N = 1e6 + 10;
char ch[N];
int n, sa[N], rk[N], height[N];
void getsa(char ch[] = ch, int sa[] = sa, int rk[] = rk) {
int m = 127;
static int cntmx[N], rk2[N], key[N], id[N];
memset(cntmx+1, 0, m * sizeof(*cntmx));
rep(i,1,n) rk[i] = ch[i], cntmx[rk[i]]++;
rep(i,1,m) cntmx[i] += cntmx[i-1];
pre(i,n,1) sa[cntmx[rk[i]] --] = i;
for (int w = 1; ; w <<= 1) {
int p = 0;
for (register int i = n; i > n - w; -- i) id[++p] = i;
rep(i,1,n) if (sa[i] > w) id[++p] = sa[i] - w;
memset(cntmx + 1, 0, m * sizeof(*cntmx));
rep(i,1,n) key[i] = rk[id[i]], cntmx[key[i]]++;
rep(i,1,m) cntmx[i] += cntmx[i-1];
pre(i,n,1) sa[cntmx[key[i]] --] = id[i];
memcpy(rk2+1, rk+1, n * sizeof(*rk2));
p = 0;
rep(i,1,n) rk[sa[i]] = (
rk2[sa[i]] == rk2[sa[i-1]] and
rk2[sa[i] + w] == rk2[sa[i-1] + w]
) ? p : ++p;
if (p == n) { rep(i,1,n) sa[rk[i]] = i; break; }
m = p;
}
}
void getheight() {
for (int i = 1, k = 0; i <= n; i++) {
if (rk[i] == 0) continue;
if (k) k--;
while (ch[i + k] == ch[sa[rk[i] - 1] + k]) k++;
height[rk[i]] = k;
}
}
const int inf = 1e8;
class SegmentBeats {
#define ls (p << 1)
#define rs (p << 1 | 1)
#define lzy(p) seg[p].lzy
#define sum(p) seg[p].sum
#define firmx(p) seg[p].firmx
#define secmx(p) seg[p].secmx
#define cntmx(p) seg[p].cntmx
private :
struct node {
int firmx, secmx, cntmx, lzy, sum;
} seg[N << 2];
void ps_p(int p) {
sum(p) = sum(ls) + sum(rs);
if (firmx(ls) == firmx(rs)) {
firmx(p) = firmx(ls);
secmx(p) = max(secmx(ls), secmx(rs));
cntmx(p) = cntmx(ls) + cntmx(rs);
} else if (firmx(ls) > firmx(rs)) {
firmx(p) = firmx(ls);
secmx(p) = max(secmx(ls), firmx(rs));
cntmx(p) = cntmx(ls);
} else {
firmx(p) = firmx(rs);
secmx(p) = max(firmx(ls), secmx(rs));
cntmx(p) = cntmx(rs);
}
}
void ps_t(int p, int v) {
if (firmx(p) <= v) return;
sum(p) -= (firmx(p) - v) * cntmx(p);
firmx(p) = lzy(p) = v;
}
void ps_d(int p) {
if (lzy(p) == -inf) return;
ps_t(ls, lzy(p)), ps_t(rs, lzy(p));
lzy(p) = -inf;
}
void upd(int p, int l, int r, int L, int R, int v) {
if (firmx(p) <= v) return;
if (L <= l and r <= R and secmx(p) < v) return ps_t(p, v), void();
ps_d(p); int mid = l + r >> 1;
if (L <= mid) upd(ls, l, mid, L, R, v);
if (mid < R) upd(rs, mid + 1, r, L, R, v);
ps_p(p);
}
int qry(int p, int l, int r, int L, int R) {
if (L <= l and r <= R) return sum(p);
ps_d(p); int mid = l + r >> 1, ret = 0;
if (L <= mid) ret += qry(ls, l, mid, L, R);
if (mid < R) ret += qry(rs, mid + 1, r, L, R);
ps_p(p);
return ret;
}
public :
void build(int p = 1, int l = 1, int r = n) {
lzy(p) = -inf;
if (l == r) {
sum(p) = firmx(p) = inf, secmx(p) = -inf, cntmx(p) = 1;
return;
} int mid = l + r >> 1;
build(ls, l, mid), build(rs, mid + 1, r);
ps_p(p);
}
void upd(int l, int r, int v) { upd(1, 1, n, l, r, v); }
int qry(int l, int r) { return qry(1, 1, n, l, r); }
} Tr;
signed main() {
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> ch + 1; n = strlen(ch + 1);
getsa(); getheight();
int ans = 0;
Tr.build();
pre(i,n,1) {
if (i < n) ans += Tr.qry(i + 1, n);
Tr.upd(i, n, height[i]);
}
cout << 1ll * n * (n - 1) * (n + 1) / 2 - 2 * ans << endl;
}
给一个字符串 \(S\)。有 \(q\) 次询问,每次询问给定两个排名 \(rk_1, rk_2\)。你需要回答将 \(S\) 的所有本质不同字符串排序后排名分别为 \(rk_1, rk_2\) 的两个字符串 \(s_1, s_2\) 的 \(|最长公共前缀|^2 + |最长公共后缀|^2\) 的值。
\(N≤100000, Q≤100000\),字符串 \(S\) 只由小写字母组成。
首先我们需要定位这个串是哪个前缀的子串。
做 SA 求出需要的信息,然后就能定位了。我们按照 \(\text{rk}\) 一个一个地确定每个后缀能贡献的子串数。显然贡献就是 \(后缀长度 - \text{LCP } 长度\)。也就是 \((n - \text{sa} + 1) - \text{height}\)。求出这个后作前缀和,我们就能通过二分找到确定排名的子串串首所在位置以及长度了。记作 \(id\) 和 \(len\)。
随后考虑如何计算答案。
首先最长公共前缀是简单的,我们直接求 \([id_1 + 1, id_2]\) 段内的 \(\text{height}\) 最小值即可。
最长后缀呢?知道 \(id\) 和 \(len\) 后不难得到其在翻转串上的串首位置。因此我们对反串求出 SA,对应到反串上求得 \(\text{height}\) 最小值。
两个最小值分别和 \(\min(len_1, len_2)\) 取 \(\min\) 即可。
因为得对反串求解 SA,这里封装了一下。然后想着封装都封装了,\(\text{build}()\) 就是个接口,为什么不打一下 SA-IS 呢?
然后就打了一下。很难相信的是 bzoj 上没人实现 SA-IS,混了个最优解。
code
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
const int N = 2e5 + 10;
int n, q;
ll t1, t2;
template <typename _Tp = char, const int siz = 127>
struct SA {
_Tp ch[N];
int n = -1, sa[N], rk[N], heg[N];
int cnt[N], rk2[N], key[N], id[N];
_Tp* begin() { return ch + 1; }
const _Tp operator [] (const int & p) const { return ch[p]; }
_Tp & operator [] (const int & p) { return ch[p]; }
void getsa() {
int m = siz;
memset(cnt + 1, 0, sizeof(*cnt) * m);
rep(i,1,n) rk[i] = ch[i], cnt[rk[i]]++;
rep(i,1,m) cnt[i] += cnt[i - 1];
pre(i,n,1) sa[cnt[rk[i]] --] = i;
for (int w = 1, p; ; w <<= 1) {
p = 0;
pre(i,n,n-w+1) id[++ p] = i;
rep(i,1,n) if (sa[i] > w) id[++ p] = sa[i] - w;
memset(cnt + 1, 0, sizeof(*cnt) * m);
rep(i,1,n) key[i] = rk[id[i]], cnt[key[i]] ++;
rep(i,1,m) cnt[i] += cnt[i - 1];
pre(i,n,1) sa[cnt[key[i]] --] = id[i];
memcpy(rk2 + 1, rk + 1, sizeof(*rk2) * n);
p = 0;
rep(i,1,n) rk[sa[i]] = (
rk2[sa[i]] == rk2[sa[i - 1]] and
rk2[sa[i] + w] == rk2[sa[i - 1] + w]
) ? p : ++ p;
if (p == n) { rep(i,1,n) sa[rk[i]] = i; break; }
m = p;
}
}
void getheg() {
for (int i = 1, k = 0; i <= n; ++ i) {
if (rk[i] == 0) continue;
if (k) -- k;
while (ch[i + k] == ch[sa[rk[i] - 1] + k]) ++ k;
heg[rk[i]] = k;
}
}
int st[N][20], lgv[N];
void build() {
if (n <= 0) n = strlen(begin());
getsa(); getheg();
rep(i,1,n) st[i][0] = heg[i];
rep(i,2,n) lgv[i] = lgv[i >> 1] + 1;
rep(i,1,lgv[n]) for (int j = 1; j + (1 << i) - 1 <= n; ++ j)
st[j][i] = min(st[j][i - 1], st[j + (1 << i - 1)][i - 1]);
}
int query(int l, int r) {
if (l == r) return n + 1;
if (l > r) swap(l, r); ++ l;
int d = lgv[r - l + 1];
return min(st[l][d], st[r - (1 << d) + 1][d]);
}
};
SA<char, 127> str[2];
ll lv[N], rv[N];
int get(ll L) {
int l = 1, r = n, mid;
while (l <= r) {
mid = l + r >> 1;
if (lv[mid] <= L and L <= rv[mid]) return mid;
else if (lv[mid] > L) r = mid - 1;
else l = mid + 1;
} assert(0);
}
signed main() {
ios::sync_with_stdio(false); cin.tie(0), cout.tie(0);
cin >> str[0].n >> q >> str[0].begin();
str[1].n = n = str[0].n;
rep(i,1,str[1].n) str[1][i] = str[0][n - i + 1]; str[1][str[1].n + 1] = 0;
str[0].build(); str[1].build();
rep(i,1,n) lv[i] = rv[i - 1] + 1, rv[i] = lv[i] + n - str[0].sa[i] - str[0].heg[i];
while (q --) {
cin >> t1 >> t2;
if (t1 > rv[n] or t2 > rv[n]) {
cout << "-1\n";
continue;
}
int id1 = get(t1), id2 = get(t2);
int len1 = str[0].heg[id1] + t1 - lv[id1] + 1,
len2 = str[0].heg[id2] + t2 - lv[id2] + 1;
int ans1 = min( { len1, len2, str[0].query(id1, id2) } ),
ans2 = min( { len1, len2, str[1].query(str[1].rk[ n - (str[0].sa[id1] + len1 - 1) + 1 ], str[1].rk[ n - (str[0].sa[id2] + len2 - 1) + 1] ) } );
cout << 1ll * ans1 * ans1 + 1ll * ans2 * ans2 << '\n';
}
}
struct SA with SA-IS
template <typename _Tp = char, const int siz = 127>
struct SA {
_Tp ch[N];
int n = -1, sa[N], rk[N], heg[N];
int a[N], tr[siz + 5], cur[N];
int sum[N], bse[N << 1], *_t = bse;
_Tp* begin() { return ch + 1; }
const _Tp operator [] (const int & p) const { return ch[p]; }
_Tp & operator [] (const int & p) { return ch[p]; }
#define _MemAlloc(pool, size, tag) (tag = pool, pool += size)
#define pushs(x) (sa[cur[a[x]] --] = x)
#define pushi(x) (sa[cur[a[x]] ++] = x)
#define inds(lms) \
rep(i,1,n) sa[i] = -1, sum[i] = 0; \
rep(i,1,n) sum[a[i]] ++; \
rep(i,1,n) sum[i] += sum[i - 1]; \
rep(i,1,n) cur[i] = sum[i]; \
pre(i,m,1) pushs(lms[i]); \
rep(i,1,n) cur[i] = sum[i - 1] + 1; \
rep(i,1,n) if (sa[i] > 1 and !tp[sa[i] - 1]) pushi(sa[i] - 1); \
rep(i,1,n) cur[i] = sum[i]; \
pre(i,n,1) if (sa[i] > 1 and tp[sa[i] - 1]) pushs(sa[i] - 1);
inline void SA_IS(int n, int* a) {
int* tp; _MemAlloc(_t, n + 1, tp); tp[n] = 1;
int* p; _MemAlloc(_t, n + 2, p);
pre(i,n-1,1) tp[i] = (a[i] == a[i + 1]) ? tp[i + 1] : (a[i] < a[i + 1]);
int m = 0, tot = 0;
rep(i,1,n) rk[i] = (tp[i] and !tp[i - 1]) ? (p[++ m] = i, m) : -1;
inds(p);
int* a1; _MemAlloc(_t, m + 1, a1);
p[m + 1] = n;
for (int i = 1, x, y; i <= n; ++ i) if ((x = rk[sa[i]]) != -1) {
if (tot == 0 or p[x + 1] - p[x] != p[y + 1] - p[y]) ++ tot;
else for (int p1 = p[x], p2 = p[y]; p2 <= p[y + 1]; ++ p1, ++ p2)
if ((a[p1] << 1 | tp[p1]) != (a[p2] << 1 | tp[p2])) { ++ tot; break; }
a1[y = x] = tot;
}
if (tot == m) rep(i,1,m) sa[a1[i]] = i;
else SA_IS(m, a1);
rep(i,1,m) a1[i] = p[sa[i]];
inds(a1);
}
int st[N][20], lgv[N];
void build() {
if (n <= 0) n = strlen(begin());
rep(i,1,n) tr[ch[i]] = 1;
rep(i,1,siz + 1) tr[i] += tr[i - 1];
rep(i,1,n) a[i] = tr[ch[i]] + 1;
a[n + 1] = 1;
SA_IS(n + 1, a);
rep(i,1,n) sa[i] = sa[i + 1];
rep(i,1,n) rk[sa[i]] = i;
for (int i = 1, k = 0; i <= n; ++ i) {
if (rk[i] == 0) continue;
if (k) -- k;
while (ch[i + k] == ch[sa[rk[i] - 1] + k]) ++ k;
heg[rk[i]] = k;
}
rep(i,1,n) st[i][0] = heg[i];
rep(i,2,n) lgv[i] = lgv[i >> 1] + 1;
rep(i,1,lgv[n]) for (int j = 1; j + (1 << i) - 1 <= n; ++ j)
st[j][i] = min(st[j][i - 1], st[j + (1 << i - 1)][i - 1]);
}
int query(int l, int r) {
if (l == r) return n + 1;
if (l > r) swap(l, r); ++ l;
int d = lgv[r - l + 1];
return min(st[l][d], st[r - (1 << d) + 1][d]);
}
};
长长长,不粘。
SA 的话就写 SA 的做法吧。
SAM tomorrow(?) !
一杯酒可以看作一个后缀,两杯酒是 \(r\) 相似的,当且仅当从这两杯酒所在位置开始的后缀的 \(\text{LCP} \ge r\)。
我们发现,两个串是 \(r \ge 0\) 相似的,那必定是 \(r - 1\) 相似的。因此可以先求出恰好 \(r\) 相似的信息,再作后缀和得到最终答案。
考虑两个后缀 \(p < q\) 在 \(\min\limits_{i = p+1}^q \text{height}(i)\) 处统计答案。这里不妨 \(p = p + 1\)。然后我们发现一段信息会在最小值处统计,这启发我们按由大到小的顺序插入 \(\text{height}\) 值。插入 \(\text{height} = k\) 时考虑是否能合并左右两段。每段记录长度,合并时可以选出的酒数量是两段长度之积,这里需要记录一个历史数量和。然后最大值考虑有负数,是两段的最大值之积和最小值之积中的较大值。
这可以很简单地使用并查集维护。
取后缀和/后缀 \(\max\) 可以得到答案。
code
#include <bits/stdc++.h>
using namespace std; using ll = long long;
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
const int N = 5e6 + 10;
const int inf = 0x3f3f3f3f;
const ll infll = 0x3f3f3f3f3f3f3f3fll;
int n, a[N];
ll ans1[N], ans2[N], val1, val2 = -infll;
char ch[N];
vi buk[N];
int sa[N], rk[N], height[N];
void getsa(char ch[] = ch, int sa[] = sa, int rk[] = rk) {
int m = 127;
static int cnt[N], rk2[N], key[N], id[N];
memset(cnt+1, 0, m << 2);
rep(i,1,n) rk[i] = ch[i], cnt[rk[i]]++;
rep(i,1,m) cnt[i] += cnt[i-1];
pre(i,n,1) sa[cnt[rk[i]] --] = i;
for (int w = 1; ; w <<= 1) {
int p = 0;
for (register int i = n; i > n - w; -- i) id[++p] = i;
rep(i,1,n) if (sa[i] > w) id[++p] = sa[i] - w;
memset(cnt + 1, 0, m << 2);
rep(i,1,n) key[i] = rk[id[i]], cnt[key[i]]++;
rep(i,1,m) cnt[i] += cnt[i-1];
pre(i,n,1) sa[cnt[key[i]] --] = id[i];
memcpy(rk2+1, rk+1, n << 2);
p = 0;
rep(i,1,n) rk[sa[i]] = (
rk2[sa[i]] == rk2[sa[i-1]] and
rk2[sa[i] + w] == rk2[sa[i-1] + w]
) ? p : ++p;
if (p == n) { rep(i,1,n) sa[rk[i]] = i; break; }
m = p;
}
}
void getheight(char s[] = ch) {
for (int i = 1, k = 0; i <= n; i++) {
if (rk[i] == 0) continue;
if (k) k--;
while (s[i + k] == s[sa[rk[i] - 1] + k]) k++;
height[rk[i]] = k;
}
}
int fa[N], mx[N], mn[N], sum[N];
int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); }
void merge(int x, int y) {
x = find(x), y = find(y);
if (x == y) return;
val1 += 1ll * sum[x] * sum[y], val2 = max( { val2, 1ll * mx[x] * mx[y], 1ll * mn[x] * mn[y] } );
fa[x] = y, sum[y] += sum[x], mx[y] = max(mx[x], mx[y]), mn[y] = min(mn[x], mn[y]);
}
signed main() {
cin >> n >> ch + 1;
rep(i,1,n) cin >> a[i];
getsa(), getheight();
rep(i,1,n) fa[i] = i, sum[i] = 1, mx[i] = mn[i] = a[sa[i]], buk[height[i]].eb(i);
pre(i,n-1,0) {
for (auto x : buk[i]) merge(x, x - 1);
if (val1) ans1[i] = val1, ans2[i] = val2;
}
rep(i,0,n-1) cout << ans1[i] << ' ' << ans2[i] << '\n';
}
给定一个 \(n\times m\) 的矩形,求上下对称且左右对称的正方形子矩形个数。
\(1\le n, m \le 10^3\)。
发现数据范围支持 \(O(nm \log n)\),因此考虑枚举回文中心后二分判断。判断时需要 \(O(1)\) 检查两个子矩形是否相同。这不得不令人想到哈希。
于是出现了二维哈希这种东西。具体地,我们需要选择两个底数(不要两个模数),横着作一遍哈希,竖着作一遍哈希。查询时类似二维前缀和。
然后没了。具体看代码。
当然你也可以二维 manacher。luogu 题解
code
#include <bits/stdc++.h>
using namespace std;
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
const int N = 1e3 + 5, bse1 = 131, bse2 = 13131;
int n, m, l, r, mid;
long long ans, pw1[N], pw2[N];
struct hash_2d {
int v[N][N];
long long hsh[N][N];
int* operator[] (const int & p) { return v[p]; }
const int* operator[] (const int & p) const { return v[p]; }
void init() {
rep(i,1,n) rep(j,1,m) hsh[i][j] = hsh[i][j - 1] * bse1 + v[i][j];
rep(i,1,n) rep(j,1,m) hsh[i][j] = hsh[i - 1][j] * bse2 + hsh[i][j];
}
long long get(int x1, int y1, int x2, int y2) {
return hsh[x2][y2] - hsh[x1 - 1][y2] * pw2[x2 - x1 + 1] - hsh[x2][y1 - 1] * pw1[y2 - y1 + 1]
+ hsh[x1 - 1][y1 - 1] * pw1[x2 - x1 + 1] * pw2[y2 - y1 + 1];
}
} a, b, c;
bool check(int x, int y, int len) { // (x - len + 1, y - len + 1) -> (x, y)
if (x > n or y > m or x < len or y < len) return 0;
long long tmp1 = a.get(x - len + 1, y - len + 1, x, y);
int tx = n - (x - len);
long long tmp2 = b.get(tx - len + 1, y - len + 1, tx, y);
int ty = m - (y - len);
long long tmp3 = c.get(x - len + 1, ty - len + 1, x, ty);
return (tmp1 == tmp2) and (tmp2 == tmp3);
}
signed main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
pw1[0] = pw2[0] = 1;
rep(i,1,1000) pw1[i] = pw1[i - 1] * bse1;
rep(i,1,1000) pw2[i] = pw2[i - 1] * bse2;
cin >> n >> m;
rep(i,1,n) rep(j,1,m) cin >> a[i][j];
rep(i,1,n) rep(j,1,m) b[i][j] = a[n - i + 1][j], c[i][j] = a[i][m - j + 1];
a.init(); b.init(); c.init();
rep(i,1,n) rep(j,1,m) {
l = 1, r = max(n, m);
while (l <= r) {
mid = l + r >> 1;
if (check(i + mid, j + mid, mid << 1)) l = mid + 1;
else r = mid - 1;
} ans += l - 1;
}
rep(i,1,n) rep(j,1,m) {
l = 1, r = max(n, m);
while (l <= r) {
mid = l + r >> 1;
if (check(i + mid, j + mid, mid << 1 | 1)) l = mid + 1;
else r = mid - 1;
} ans += l - 1;
}
cout << ans + m * n << '\n';
}
以下是博客签名,与正文无关。
请按如下方式引用此页:
本文作者 joke3579,原文链接:https://www.cnblogs.com/joke3579/p/chitchat221222.html。
遵循 CC BY-NC-SA 4.0 协议。
请读者尽量不要在评论区发布与博客内文完全无关的评论,视情况可能删除。