题解:[HEOI2016/TJOI2016] 字符串

[HEOI2016/TJOI2016] 字符串

题目链接

能单独写出一篇题解,说明这个题对我来说很有趣 qwq。

不过这个题感觉不太能到黑(写完题解后:并不是),不过已经降不了了(喜

首先看题发现给定一个字符串,然后询问这个串一个区间内的所有子串和这个串另一个区间的最长公共前缀。

首先不要读错啊,这另一个串是整个串而不是子串。

首先暴力匹配是很简单的就是很暴力的在这个区间的每个后缀遍历然后区间查询后去最大值。

但这很没有前途啊,考虑怎么优化。

其实很多查询都没有用,因为区间越大值越小,所以答案一定出现在这个询问串的前后不远,容易发现就是前驱后继。

证明只考虑向前的贡献,因为到这个前驱有一个最小值,而更靠前的查询都会经过这个最小值,所以一定不优。

具体操作就是在前面那个区间的 \(rank\) 单独拎出来,然后排序,在求出后面那个区间的 \(rank\) 的前驱后继,询问就好。

可是出现问题了!

就是靠后的字符串会被后面的长度限制了。

举个例子就是假设现在对 \(abb\) 询问 \([1,2]\),但是这个串是 \(abb\),那答案应该是 \(2\),但是如果直接查询我们查询的整个后缀,所以会查询出 \(3\),那么就要避免这种情况,固然想到枚举答案长度,因为这样就知道有哪些是合法的了,而且满足一个长度上界的合法串一定构成一个前缀的区间。

那么很愉快的就发现其长度具有单调性,直接上二分答案即可。

现在问题只有一个了,怎么快速将一个区间内的 \(rank\) 抽出来查询前驱后继呢?

这东西好像可持久化平衡树貌似也不好做,那只能求助强大的主席树了。

因为主席树可以查询区间排名第 \(k\) 小,所以理论上也能求区间前驱后继了。

开一颗动态开点权值线段树,然后节点维护这个区间内的值的数量。

查询直接主席树经典差分即可。

怎么查询前驱后继呢?

一个简单的思路就是直接维护区间最小最大值然后查询,但是这东西不能差分,所以只能从数量入手了。

以查询前驱为例子,考虑分类讨论。

当查询的 \(k\) 小于等于 \(mid\) 时,说明其落在了左边,而前驱不可能落在右边,直接查询左儿子即可。

\(k\) 大于 \(mid\) 时,说明其落在了右边,此时应该先查询右儿子,然后返回其值,如果是棍母就说明这个区间内根本就没有值,此时应该去查询左儿子。

但是这个时间复杂度可能有点假,但是如果把这个询问具象成一个区间查询 \([1,k]\),所以这个询问本质就是一个区间询问的复杂度,是 \(O(\log n)\)

更具体一点就是因为真正产生答案的就只有一个地方所以其他所有询问到的区间一定包含这个点,所以是 \(O(\log n)\) 级别的,并且这个区间查询最多产生 \(O(\log n)\) 个无用查询区间。

所以加上二分的复杂度,总体就是 \(O(n\log^2 n)\)

那么这个题就做完了!

收回开头的话,这题一点也不水。。。

点击查看代码
const int V = 1100, mod = 1e9 + 7, B = 300, MAXK = 22, N = 1e5 + 10; // !!!!!!!!

int root[N];
int x[N], y[N], rk[N], h[N], cnt[N], sa[N], st[MAXK][N], logn[N];
int n, m, q, a, b, c, d;
string s;

class SemTree
{
    public :

    int cnt[N << 7], ls[N << 7], rs[N << 7], tot;

    void PushUp(int id) {cnt[id] = cnt[lid] + cnt[rid];}

    void Update(int &id, int u, int l, int r, int pos)
    {
        id = ++tot;
        cnt[id] = cnt[u], ls[id] = ls[u], rs[id] = rs[u];
        if (l == r) return cnt[id]++, void();
        int mid = (l + r) >> 1;
        if (pos <= mid) Update(lid, ls[u], l, mid, pos);
        else Update(rid, rs[u], mid + 1, r, pos);
        PushUp(id);
    }

    int FindPre(int id, int u, int l, int r, int k)
    {
        int h = cnt[id] - cnt[u], mid = (l + r) >> 1;
        if (!h) return 0;
        if (l == r) return l;
        // cerr << l << ' ' << r << ' ' << rs[id] << '\n';
        if (k <= mid)
        {
            return FindPre(ls[id], ls[u], l, mid, k);
        }
        else
        {
            h = FindPre(rs[id], rs[u], mid + 1, r, k);
            if (h) return h;
            else return FindPre(ls[id], ls[u], l, mid, k);
        }
    }

    int FindSuf(int id, int u, int l, int r, int k)
    {
        int h = cnt[id] - cnt[u], mid = (l + r) >> 1;
        if (!h) return 0;
        if (l == r) return l;
        if (k <= mid)
        {
            h = FindSuf(ls[id], ls[u], l, mid, k);
            if (h) return h;
            else return FindSuf(rs[id], rs[u], mid + 1, r, k);
        }
        else
        {
            return FindSuf(rs[id], rs[u], mid + 1, r, k);
        }
    }
}T;

class SuffixArray
{
    public :

    void tsort()
    {
        for (int i = 1; i <= n; i++) cnt[x[i]]++;
        for (int i = 1; i <= m; i++) cnt[i] += cnt[i - 1];
        for (int i = n; i >= 1; i--) sa[cnt[x[y[i]]]--] = y[i], y[i] = 0;
        for (int i = 1; i <= m; i++) cnt[i] = 0;
    }

    void GetSA()
    {
        m = 255;
        for (int i = 1; i <= n; i++) x[i] = s[i - 1], y[i] = i;
        tsort();
        for (int w = 1; w < n; w <<= 1)
        {
            int tot = 0;
            for (int i = n - w + 1; i <= n; i++) y[++tot] = i;
            for (int i = 1; i <= n; i++) if (sa[i] > w) y[++tot] = sa[i] - w;
            tsort();
            swap(x, y);
            x[sa[1]] = tot = 1;
            for (int i = 1; i <= n; i++) x[sa[i]] = (y[sa[i]] == y[sa[i - 1]] && y[sa[i] + w] == y[sa[i - 1] + w]) ? tot : ++tot;
            m = tot;
        }
    }

    void GetHeight()
    {
        for (int i = 1; i <= n; i++) rk[sa[i]] = i;
        for (int i = 1, k = 0; i <= n; i++)
        {
            if (k) --k;
            while (k <= n && s[sa[rk[i]] + k - 1] == s[sa[rk[i] - 1] + k - 1]) ++k;
            h[rk[i]] = k;
        }
    }
}SA;

class STable
{
    public :

    void Build()
    {
        for (int i = 1; i <= n; i++) st[0][i] = h[i];
        for (int i = 2; i <= n; i++) logn[i] = logn[i / 2] + 1;
        for (int i = 1; i < MAXK; i++)
        {
            for (int j = 1; j + (1 << (i - 1)) <= n; j++)
            {
                st[i][j] = min(st[i - 1][j], st[i - 1][j + (1 << (i - 1))]);
            }
        }
    }

    int Query(int l, int r)
    {
        if (l > r) return 0;
        int k = logn[r - l + 1];
        return min(st[k][l], st[k][r - (1 << k) + 1]);
    }
}ST;

bool check(int len, int l, int r, int k)
{
    if (l > r) return false;
    int pre = T.FindPre(root[r], root[l - 1], 1, n, k), suf = T.FindSuf(root[r], root[l - 1], 1, n, k);
    // cerr << suf << ' ';
    if (pre == k || suf == k) return true;
    return max(ST.Query(pre + 1, k), ST.Query(k + 1, suf)) >= len;
}

signed main()
{
    // freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
    ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);

    cin >> n >> q;
    cin >> s;

    SA.GetSA(), SA.GetHeight(); // prework
    ST.Build();
    // for (int i = 1; i <= n; i++) cin >> rk[i];
    // for (int i = 1; i <= n; i++) cerr << rk[i] << ' ';
    for (int i = 1; i <= n; i++) T.Update(root[i], root[i - 1], 1, n, rk[i]);
    // for (int i = 1; i <= q; i++)
    // {
    //     int l, r, k; cin >> l >> r >> k;
    //     cout << T.FindPre(root[r], root[l - 1], 1, inf, k) << '\n';
    // }

    for (int i = 1; i <= q; i++)
    {
        cin >> a >> b >> c >> d;
        int l = 1, r = d - c + 1, pos = 0, mid, k = rk[c];
        while (l <= r)
        {
            mid = (l + r) >> 1;
            if (check(mid, a, b - mid + 1, k)) pos = mid, l = mid + 1; // calc [l, r] may be wrong...
            else r = mid - 1;
        }
        cout << pos << '\n';
    }

    QED;
}
posted @ 2025-08-07 22:05  QEDQEDQED  阅读(25)  评论(2)    收藏  举报