[洛谷P2178][BZOJ4199][UOJ#131][NOI2015]品酒大会(绝世好题)三种做法

更深地理解了 SA 和 SAM 。

Address

洛谷:https://www.luogu.org/problemnew/show/P2178
BZOJ:https://www.lydsy.com/JudgeOnline/problem.php?id=4199
UOJ:http://uoj.ac/submissions?submitter=xyz32768

Meaning

输入 n n 和一个长度为 n 的字符串,再输入 n n 整数(可能为负),第 i 个整数为 ai a i ,对于每个 i[0,n) i ∈ [ 0 , n ) ,求:( LCP(p,q) LCP ( p , q ) 为以 p p 开始的后缀和以 q 开始的后缀的最长公共前缀长度)
(1)满足

1p<qn,LCP(p,q)i 1 ≤ p < q ≤ n , LCP ( p , q ) ≥ i

的二元组 (p,q) ( p , q ) 的个数。
(2)
max1p<qnLCP(p,q)iap×aq max 1 ≤ p < q ≤ n LCP ( p , q ) ≥ i a p × a q

n3×105 n ≤ 3 × 10 5

First Of All

[BZOJ3238][AHOI2013]差异 的升级版。
同样是一道字符串题。
可以使用 后缀数组 / 后缀自动机 来做。

Solution 1

Algorithm:后缀数组+单调栈

考虑把问题拆开,对于每个 i i 求出所有满足 LCP(p,q)=i 的二元组 (p,q) ( p , q ) 的个数以及 ap×aq a p × a q 的最大值,然后个数做一遍前缀后缀和,最大值求一遍后缀最大值。
我们知道,一个字符串的 height h e i g h t 求出之后,任意两个不同后缀的最长共前缀都能用 height h e i g h t 中一段区间的最小值来表示。
而显然 height h e i g h t 的最小值最多只有 n1 n − 1 个取值。
对于每个 2in 2 ≤ i ≤ n ,用单调栈预处理出:
pre[i] p r e [ i ] 为满足 j<i j < i height[j]height[i] h e i g h t [ j ] ≤ h e i g h t [ i ] 的最大的 j j
nxt[i] 为满足 j>i j > i height[j]<height[i] h e i g h t [ j ] < h e i g h t [ i ] 的最小的 j j
注意,一个为 height[j]height[i] 另一个为 height[j]<height[i] h e i g h t [ j ] < h e i g h t [ i ] 是为了避免重复。
这样,如果 l[pre[i]+1,i],r[i,nxt[i]1] l ∈ [ p r e [ i ] + 1 , i ] , r ∈ [ i , n x t [ i ] − 1 ] ,那么区间 [l,r] [ l , r ] 的最小值(相同情况下位置编号最小)为 height[i] h e i g h t [ i ] 。由于这样的区间最小值只有一个,因此这样就不会重复也不遗漏了。
于是,我们找到了 (ipre[i])×(nxt[i]i) ( i − p r e [ i ] ) × ( n x t [ i ] − i ) LCP LCP 长度为 height[i] h e i g h t [ i ] 的后缀对。上面所说的区间 [l,r] [ l , r ] 对应原串中的后缀对就是 (sa[l]1,sa[r]) ( s a [ l ] − 1 , s a [ r ] ) 。于是就可以以 arank[i] a r a n k [ i ] 为关键字建立一个序列,每次给出两个区间 [u,v][l,r] [ u , v ] [ l , r ] ,求:

maxp[u,v],q[l,r]arank[p]×arank[q] max p ∈ [ u , v ] , q ∈ [ l , r ] a r a n k [ p ] × a r a n k [ q ]

可以用 rmq 实现这个询问。但要注意,由于 a a 有负数,因此不能盲目地在区间 [u,v][l,r] 中各选一个最大值相乘,而应该分别选出最大值和最小值后两两相乘。
时间复杂度 O(nlogn) O ( n log ⁡ n )

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Rof(i, a, b) for (i = a; i >= b; i--)
#define Pow(i, a, b) for (i = a; i <= b; i <<= 1, swap(x, y))
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
typedef long long ll; const int N = 3e5 + 5, E = 21;
int n, m, a[N], sa[N], rank[N], height[N], w[N], rmq[N][E], rnq[N][E], Log[N], top,
stk[N], pre[N], nxt[N]; ll _cnt[N], _max[N]; char s[N];
void Noi2015() {
    int i, j, k, *x = rank, *y = height; m = 26;
    For (i, 1, n) w[x[i] = s[i] - 'a' + 1]++;
    For (i, 2, m) w[i] += w[i - 1]; For (i, 1, n) sa[w[x[i]]--] = i;
    Pow (k, 1, n - 1) {
        int tt = 0; For (i, n - k + 1, n) y[++tt] = i;
        For (i, 1, n) if (sa[i] > k) y[++tt] = sa[i] - k;
        memset(w, 0, sizeof(w)); For (i, 1, n) w[x[i]]++;
        For (i, 2, m) w[i] += w[i - 1]; Rof (i, n, 1)
            sa[w[x[y[i]]]--] = y[i]; m = 0; For (i, 1, n) {
            int u = sa[i], v = sa[i - 1];
            y[u] = x[u] != x[v] || x[u + k] != x[v + k] ? ++m : m;
        }
        if (m == n) break;
    }
    if (y != rank) copy(y, y + n + 1, rank); k = 0; For (i, 1, n) {
        if (k) k--; int u = sa[rank[i] - 1];
        while (s[u + k] == s[i + k]) k++; height[rank[i]] = k;
    }
    Log[0] = -1; For (i, 1, n) Log[i] = Log[i >> 1] + 1;
    For (i, 1, n) rmq[rank[i]][0] = rnq[rank[i]][0] = a[i];
    For (j, 1, 19) For (i, 1, n - (1 << j) + 1)
        rmq[i][j] = max(rmq[i][j - 1], rmq[i + (1 << j - 1)][j - 1]),
        rnq[i][j] = min(rnq[i][j - 1], rnq[i + (1 << j - 1)][j - 1]);
    stk[top = 0] = 1; For (i, 2, n) {
        while (top && height[stk[top]] > height[i]) top--;
        pre[stk[++top] = i] = stk[top - 1];
    }
    stk[top = 0] = n + 1; Rof (i, n, 2) {
        while (top && height[stk[top]] >= height[i]) top--;
        nxt[stk[++top] = i] = stk[top - 1];
    }
}
int qmax(int l, int r) {
    int x = Log[r - l + 1]; return max(rmq[l][x], rmq[r - (1 << x) + 1][x]);
}
int qmin(int l, int r) {
    int x = Log[r - l + 1]; return min(rnq[l][x], rnq[r - (1 << x) + 1][x]);
}
int main() {
    int i; n = read(); scanf("%s", s + 1); For (i, 1, n) a[i] = read();
    For (i, 0, n - 1) _max[i] = -(1ll << 62);
    Noi2015(); For (i, 2, n) {
        _cnt[height[i]] += 1ll * (i - pre[i]) * (nxt[i] - i);
        int lmax = qmax(pre[i], i - 1), lmin = qmin(pre[i], i - 1),
            rmax = qmax(i, nxt[i] - 1), rmin = qmin(i, nxt[i] - 1);
        _max[height[i]] = max(_max[height[i]], max(max(1ll * lmax * rmax,
            1ll * lmin * rmin), max(1ll * lmax * rmin, 1ll * lmin * rmax)));
    }
    Rof (i, n - 2, 0) _cnt[i] += _cnt[i + 1], _max[i] = max(_max[i], _max[i + 1]);
    For (i, 0, n - 1)
        if (_cnt[i]) printf("%lld %lld\n", _cnt[i], _max[i]);
        else puts("0 0"); return 0;
}

Solution 2

Algorithm:后缀数组+并查集

出题人要求 LCP(p,q)i LCP ( p , q ) ≥ i 而不是 =i = i ,这肯定有玄机。
还是求出 height h e i g h t 数组,但不像 Solution 1 那样把问题拆开。
我们考虑能不能设法使得求 i ≥ i 的结果时, height h e i g h t 里面只有 i ≥ i 的数。
于是可以将 height h e i g h t 数组里的数从大到小插入,按照 i i 从大到小的顺序,解决每个 i 的问题,保证解决到 i ≥ i 的问题时,被插入的数包含且仅包含所有 i ≥ i 的数。
这时候,如果已经插入 height h e i g h t 的数构成了 m m 个连续区间,第 i 个区间为 [li,ri] [ l i , r i ] ,那么满足 LCP(p,q)i LCP ( p , q ) ≥ i 的二元组 (p,q) ( p , q ) 的个数为:

i=1m(rili+1)×(rili+2)2 ∑ i = 1 m ( r i − l i + 1 ) × ( r i − l i + 2 ) 2

考虑到插入一个数时,插入的数左右的区间会被合并起来。因此用并查集维护连续区间,对于每个区间(联通集合),记录区间长度 size s i z e ,那么把两个集合 x x y 合并时,就增加了 size[x]×size[y] s i z e [ x ] × s i z e [ y ] 对二元组。同时,也可以维护乘积最大值,即记录下联通块内 a a 的最值,在 x y y 所在的区间内分别选最大值或最小值相乘。
复杂度仍然为 O(nlogn)

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Rof(i, a, b) for (i = a; i >= b; i--)
#define Pow(k, n) for (k = 1; k < n; k <<= 1, swap(x, y))
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
typedef long long ll; const int N = 3e5 + 5; const ll INF = 1ll << 62;
int n, sa[N], rank[N], height[N], m, w[N], a[N], fa[N], mina[N], minb[N], sze[N],
maxa[N], maxb[N]; char s[N]; struct cyx {int val, id;} col[N]; bool pyz[N];
ll __, ___ = -INF, i__[N], i___[N];
inline bool comp(const cyx &a, const cyx &b) {return a.val > b.val;}
int cx(int x) {if (fa[x] != x) fa[x] = cx(fa[x]); return fa[x];}
void zm(int x, int y) {
    x = cx(x); y = cx(y); __ += 1ll * sze[x] * sze[y]; sze[x] += sze[y];
    ___ = max(___, max(1ll * maxa[x] * maxb[y], 1ll * mina[x] * minb[y]));
    ___ = max(___, max(1ll * mina[x] * maxb[y], 1ll * maxa[x] * minb[y]));
    mina[x] = min(mina[x], mina[y]); maxa[x] = max(maxa[x], maxa[y]);
    minb[x] = min(minb[x], minb[y]); maxb[x] = max(maxb[x], maxb[y]);   
    sze[y] = mina[y] = maxa[y] = minb[y] = maxb[y] = 0; fa[y] = x;
}
void cyxisdalao() {
    int i, k, *x = rank, *y = height; For (i, 1, n) w[x[i] = s[i] - 'a' + 1]++;
    m = 26; For (i, 2, m) w[i] += w[i - 1]; For (i, 1, n)
        sa[w[x[i]]--] = i; Pow(k, n) {
        int tt = 0; For (i, n - k + 1, n) y[++tt] = i; For (i, 1, n)
            if (sa[i] > k) y[++tt] = sa[i] - k; memset(w, 0, sizeof(w));
        For (i, 1, n) w[x[i]]++; For (i, 2, m) w[i] += w[i - 1];
        Rof (i, n, 1) sa[w[x[y[i]]]--] = y[i]; m = 0; For (i, 1, n) {
            int u = sa[i], v = sa[i - 1];
            y[u] = x[u] != x[v] || x[u + k] != x[v + k] ? ++m : m;
        }
        if (m == n) break;
    }
    if (y != rank) copy(y, y + n + 1, rank); k = 0; For (i, 1, n) {
        if (k) k--; int u = sa[rank[i] - 1];
        while (s[u + k] == s[i + k]) k++; height[rank[i]] = k;
    }
    For (i, 2, n) col[col[i].id = i].val = height[i];
    sort(col + 2, col + n + 1, comp); For (i, 2, n) fa[i] = i;
}
int main() {
    int i, sp = 2; n = read(); scanf("%s", s + 1); For (i, 1, n) a[i] = read();
    cyxisdalao(); Rof (i, n - 1, 0) {
        while (sp <= n && col[sp].val >= i) {
            int x = col[sp].id; sze[x] = 1; mina[x] = maxa[x] = a[sa[x - 1]];
            minb[x] = maxb[x] = a[sa[x]]; pyz[x] = 1; __++;
            ___ = max(___, 1ll * a[sa[x - 1]] * a[sa[x]]);
            if (pyz[x - 1]) zm(x - 1, x); if (pyz[x + 1]) zm(x, x + 1); sp++;
        }
        i__[i] = __; i___[i] = ___;
    }
    For (i, 0, n - 1) if (!i__[i]) puts("0 0");
        else printf("%lld %lld\n", i__[i], i___[i]); return 0;
}

Solution 3

Algorithm:后缀自动机

还是和 Solution 1 一样,把问题拆开。
但这时候,要把字符串和 a a 都反转过来,这样后缀的最长公共前缀就变成了前缀的最长公共后缀。然后从后缀自动机建立 Parent 树。
而如果要求 LCS(p,q) LCS ( p , q ) ,那么只需要找到前缀 p p 对应的节点 u 和前缀 q q 对应的节点 v ,求 Parent P a r e n t 树上的 LCA(u,v)=w LCA ( u , v ) = w Maxlw M a x l w 就是最长公共后缀的长度。由于状态数是 O(n) O ( n ) 的,因此只会有 O(n) O ( n ) 种不同的最长公共后缀。枚举 LCS LCS 代表的状态节点 u u ,这时候和 AHOI 2013 差异那道题一样,对 LCS(p,q)=Maxlu 贡献的二元组个数为:

|Rightu|×(|Rightu|1)2vson(u)|Rightv|×(|Rightv|1)2 | R i g h t u | × ( | R i g h t u | − 1 ) 2 − ∑ v ∈ s o n ( u ) | R i g h t v | × ( | R i g h t v | − 1 ) 2

还没结束!还需要算出的一个东西是,假设树上的一部分点有权(如果一个点表示的是某个前缀,且是以 i i 结尾的前缀,那么权值为 a[i] ),那么需要对于每个 u u ,在 u 的子树中选出两个有权值的点 v,w v , w ,且不存在 u u 的一个子节点 x 使得 x x 的子树同时包含了 v,w (否则 v v w LCA LCA 不是 u u ),求 v 的权值与 w w 的权值积的最大值。
可以使用与树的直径类似的方法,利用最大值和次大值来求得。然而由于 a 有负数,因此要将 a a 按照正负分类处理,才能求得最大乘积。
时间复杂度 O(n) ,常数略大。

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Rof(i, a, b) for (i = a; i >= b; i--)
#define SAM(i, orz) for (; i && orz; i = T[i].fa)
#define Edge(u) for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
typedef long long ll; const int N = 3e5 + 5, M = 6e5 + 5, Inf = 0x3f3f3f3f;
const ll INF = 1ll << 62;
struct cyx {
    int maxl, go[26], fa, ri, id; void init() {
        maxl = fa = ri = id; memset(go, 0, sizeof(go));
    }
} T[M];
int n, a[N], lst, QAQ, ecnt, nxt[M], adj[M], go[M], _maxa[M], _mina[M],
_maxb[M], _minb[M]; char s[N]; ll _cnt[N], _max[N];
void add_edge(int u, int v) {
    nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v;
}
void extend(int x) {
    int c = s[x] - 'a', i = lst; T[lst = ++QAQ].init(); T[lst].ri = 1;
    T[lst].maxl = T[lst].id = x; SAM (i, !T[i].go[c]) T[i].go[c] = lst;
    if (!i) T[lst].fa = 1; else {
        int j = T[i].go[c]; if (T[i].maxl + 1 == T[j].maxl) T[lst].fa = j;
        else {
            int p; T[p = ++QAQ] = T[j]; T[p].ri = T[p].id = 0;
            T[lst].fa = T[j].fa = p; T[p].maxl = T[i].maxl + 1;
            SAM (i, T[i].go[c] == j) T[i].go[c] = p;
        }
    }
}
void dfs(int u) {
    _maxa[u] = _maxb[u] = -Inf; _mina[u] = _minb[u] = Inf;
    if (T[u].id) {
        if (a[T[u].id] >= 0) _maxa[u] = _mina[u] = a[T[u].id];
        else _maxb[u] = _minb[u] = a[T[u].id];
    }
    int __maxa = -Inf, __maxb = -Inf, __mina = Inf, __minb = Inf;
    Edge(u) dfs(v), T[u].ri += T[v].ri; _cnt[T[u].maxl] +=
        1ll * T[u].ri * (T[u].ri - 1) >> 1; Edge(u) {
        if (_maxa[v] > _maxa[u]) __maxa = _maxa[u], _maxa[u] = _maxa[v];
        else if (_maxa[v] > __maxa) __maxa = _maxa[v];
        if (_maxb[v] > _maxb[u]) __maxb = _maxb[u], _maxb[u] = _maxb[v];
        else if (_maxb[v] > __maxb) __maxb = _maxb[v];
        if (_mina[v] < _mina[u]) __mina = _mina[u], _mina[u] = _mina[v];
        else if (_mina[v] < __mina) __mina = _mina[v];
        if (_minb[v] < _minb[u]) __minb = _minb[u], _minb[u] = _minb[v];
        else if (_minb[v] < __minb) __minb = _minb[v];
        _cnt[T[u].maxl] -= 1ll * T[v].ri * (T[v].ri - 1) >> 1;
    }
    if (_maxa[u] != -Inf && __maxa != -Inf)
        _max[T[u].maxl] = max(_max[T[u].maxl], 1ll * _maxa[u] * __maxa);
    if (_minb[u] != Inf && __minb != Inf)
        _max[T[u].maxl] = max(_max[T[u].maxl], 1ll * _minb[u] * __minb);
    if (_mina[u] != Inf && _maxb[u] != -Inf)
        _max[T[u].maxl] = max(_max[T[u].maxl], 1ll * _mina[u] * _maxb[u]);
}
void orzcyxdalao() {
    int i; T[lst = QAQ = 1].init(); For (i, 1, n) extend(i);
    For (i, 2, QAQ) add_edge(T[i].fa, i); For (i, 0, n - 1) _max[i] = -INF;
    dfs(1); Rof (i, n - 2, 0) _cnt[i] += _cnt[i + 1],
        _max[i] = max(_max[i], _max[i + 1]);
}
int main() {
    int i; n = read(); scanf("%s", s + 1); Rof (i, n, 1) a[i] = read();
    For (i, 1, n >> 1) swap(s[i], s[n - i + 1]); orzcyxdalao();
    For (i, 0, n - 1) if (!_cnt[i]) puts("0 0");
        else printf("%lld %lld\n", _cnt[i], _max[i]); return 0;
}

Compare

以下是三种做法在 洛谷、BZOJ、UOJ 三个 OJ 上的 代码长度、时间、空间 三方面的比较。第三行为 Solution 1 的做法,第二行为 Solution 2 的做法,第一行为 Solution 3 。

In Luogu

这里写图片描述

In BZOJ

这里写图片描述

In UOJ

这里写图片描述

In A Word

由此可见:
代码长度: Solution 2 短于 Solution 1 , Solution 1 短于 Solution 3 。
空间效率: Solution 2 优于 Solution 1 , Solution 1 优于 Solution 3 。
运行速度: Solution 3 快于 Solution 2 , Solution 2 快于 Solution 1 。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值