首页 > 其他分享 >Luogu P5576 [CmdOI2019]口头禅 题解

Luogu P5576 [CmdOI2019]口头禅 题解

时间:2023-05-09 18:55:57浏览次数:49  
标签:rv int 题解 mid lv 区间 Luogu P5576 include

upd: 修改了一些思路的表达,帮助理解。

首先膜拜 yyc 大佬出这样的毒瘤好题。另外感谢 永无岛、xtx1092515503、hs_black 提供的思路。这里整理了一下这些思路,可能会有所启发。

题意: 给定一个字符串构成的序列,多次查询给定区间内各字符串的最长公共子串长度。

提供一种 后缀数组 + 线段树合并 的在线单 \(\log\) 实现。

1. 思路

由于本题是区间查询版的 LCS2 所以笔者首先找到了在该题中使用的一种尺取做法。

下文中,极长表示不被任何满足条件的更大区间完全包含。具体来说,当询问 \([l,r]\) 的字符串的最长公共子串时,对于后缀数组上的一个区间,满足对任意 \([l,r]\) 中的字符串,该区间至少包含其一个后缀,则该区间包含的所有后缀的最长公共前缀,显然是这些字符串的一个极长公共子串。直接通过尺取法找到包含 \([l,r]\) 中所有字符串的每个极长区间,则对应公共子串长度即为所有取出的区间在高度数组上对应的区间 \(\min\),对这些长度再取 \(\max\) 即为答案。很容易将一个后缀对应的原字符串编号联想成颜色,那么对于找到所有包含 \([l,r]\) 中每个颜色的串。

现在有多次询问,给定颜色区间,这种尺取法每次都需要 \(O(len)\) 时间,不能使用。考虑利用静态和离线的条件处理,可以发现,在需要尺取的这个序列(下称序列 \(a\))中,任何两个区间对应的 \(\min\) 值(答案)都可以快速合并;并且两个区间对应含有的所有颜色也可以用启发式合并或线段树合并快速合并。

于是可以考虑按照高度从大到小的顺序,对 \(a\) 上的每个元素,合并它两边的比它高的元素所在的段。这样每次合并完之后,这个元素所在的段的最小值就是这个元素的高度,并且我们可以得到这个段包含的所有颜色区间 \([l,r]\)。这就可以用它的高度 \(h\) 更新那些被其中某个区间完全包含的询问区间的答案。显然,这里我们依次合并出的所有区间,其答案单调不增

这样,询问是在线地找到第一次包含询问区间 \([l,r]\) 的颜色区间的对应答案。考虑把合并出的每个颜色区间 \([x,y]\) 用二元组 \((r,h)\) 表示其右端点及答案,存储在位置 \(x\) 上,则问题转变为查询 \([0,l]\) 中 \(y\ge r\) 的二元组中 \(h\) 的最大值。对该二元组按照 \(x\) 递增顺序,建立前缀值域主席树,在第 \(x\) 棵上的位置 \(y\) 插入数值 \(h\),维护值域上区间最大值即可。

2. 优化

观察可以发现瓶颈在这里的启发式合并带两个 \(\log\),但是很容易处理出一个段的所有颜色区间。而线段树合并似乎难以处理。但是 永无岛 大佬提出一种处理跨过线段树节点中点的区间的做法,可以胜任这种情况。然而这种做法有很多种具体实现,笔者使用的是个人认为比较清晰的做法。

具体来说,首先定义“跨越中点”段是指左儿子的后缀和右儿子的前缀拼成的段(这两者中也可能为空)。同时为了避免一个串被它的后代多次处理,只有这个段整体不是该节点前缀或后缀时才处理。显然任何极长颜色段就和线段树上的节点建立了一个双射。那么维护每个节点的前缀有颜色段和后缀有颜色段即可。

另一方面,如果一个节点在合并后它对应的区间长度没有变长,那么不用这个区间重复更新答案。

综上可以得到复杂度为 \(O((len + n + m)\log{n})\),空间为 \(O((len + n)\log n + m)\)。这个空间是无法通过的,但如果离线实现,可以做到空间 \(O(n\log n + len + m)\)。

更新: 如果在线段树合并中,对于只有包含一个值的值域区间对应的线段树节点,打上标记而直到合并时再建出它的一个儿子,并重复利用被删除的节点空间,可以达到 \(O(n\log n + len + m)\) 的空间复杂度。

4. 实现

离线:

#include <algorithm>
#include <cmath>
#include <cstring>
#include <iostream>
#include <vector>
using namespace std;

// Input
int n, m, len, x, y, ans[100005], str[420005], sa[420005], ht[420005];
int rt[420005], a[100005], b[420005], fa[420005], now;
pair<int, int> baf[420005];
char c;
vector<pair<short, int>> u[20010];
// Sa
namespace Sa {
int n, m, rk[420005], x[420005], y[420005];
void init() {
    m = 20002;
    for (int i = 1; i <= n; ++i) ++fa[x[i] = str[i]];
    for (int i = 2; i <= m; ++i) fa[i] += fa[i - 1];
    for (int i = n; i >= 1; --i) sa[fa[x[i]]--] = i;
    for (int k = 1; k <= n; k <<= 1) {
        int num = 0;
        for (int i = n - k + 1; i <= n; ++i) y[++num] = i;
        for (int i = 1; i <= n; ++i)
            if (sa[i] > k) y[++num] = sa[i] - k;
        for (int i = 1; i <= m; ++i) fa[i] = 0;
        for (int i = 1; i <= n; ++i) ++fa[x[i]];
        for (int i = 2; i <= m; ++i) fa[i] += fa[i - 1];
        for (int i = n; i >= 1; --i) sa[fa[x[y[i]]]--] = y[i], y[i] = 0;
        swap(x, y);
        x[sa[1]] = 1;
        num = 1;
        for (int i = 2; i <= n; ++i)
            x[sa[i]] = (y[sa[i]] == y[sa[i - 1]] && y[sa[i] + k] == y[sa[i - 1] + k]) ? num : ++num;
        if (num >= n) break;
        m = num;
    }
    int k = 0;
    for (int i = 1; i <= n; ++i) rk[sa[i]] = i;
    for (int i = 1; i <= n; ++i) {
        if (rk[i] == 1) continue;
        if (k) --k;
        int j = sa[rk[i] - 1];
        while (j + k <= n && i + k <= n && str[i + k] == str[j + k]) ++k;
        ht[rk[i]] = k;
    }
}
}
namespace Ans {
int tot;
struct Node {
    int l, r, x;
} t[400010];
inline int build(int l, int r) {
    int p = ++tot;
    if (l == r)
        sort(u[l].rbegin(), u[l].rend()), t[p].x = (u[l].empty() ? 0x3f3f3f3f : u[l].back().first);
    else {
        int mid = (l + r) >> 1;
        t[p].l = build(l, mid);
        t[p].r = build(mid + 1, r);
        t[p].x = min(t[t[p].l].x, t[t[p].r].x);
    }
    return p;
}
inline void query(int p, int l, int r, int x, int y) {
    if (r < x || t[p].x > y) return;
    int mid = (l + r) >> 1;
    if (l == r) {
        while (!u[l].empty() && u[l].back().first <= y) ans[u[l].back().second] = now, u[l].pop_back();
        t[p].x = (u[l].empty() ? 0x3f3f3f3f : u[l].back().first);
    } else {
        query(t[p].l, l, mid, x, y), query(t[p].r, mid + 1, r, x, y);
        t[p].x = min(t[t[p].l].x, t[t[p].r].x);
    }
}
}
// Segment Tree
namespace Seg {
struct Node {
    int l, r;
    short lv, rv;
} t[8000001];
int tot;
inline void pushup(int p, int l, int r) {
    int mid = (l + r) >> 1;
    if (t[p].l) t[p].lv = t[t[p].l].lv;
    if (t[p].r) t[p].rv = t[t[p].r].rv;
    if (t[t[p].l].lv == mid - l + 1) t[p].lv += t[t[p].r].lv;
    if (t[t[p].r].rv == r - mid) t[p].rv += t[t[p].l].rv;
}
inline int insert(int l, int r, int i) {
    int p = ++tot;
    if (l == r) t[p].lv = t[p].rv = 1;
    else {
        int mid = (l + r) >> 1;
        if (i <= mid) t[p].l = insert(l, mid, i), t[p].lv = t[t[p].l].lv;
        else t[p].r = insert(mid + 1, r, i), t[p].rv = t[t[p].r].rv;
    }
    return p;
}
inline int merge(int p, int q, int l, int r, int k = 0) {
    if (p == q) return p;
    if (!p || !q) return p ^ q;
    int mid = (l + r) >> 1, lt = t[t[p].l].rv, rt = t[t[p].r].lv;
    t[p].lv = max(t[p].lv, t[q].lv), t[p].rv = max(t[p].rv, t[q].rv);
    t[p].l = merge(t[p].l, t[q].l, l, mid, 1), t[p].r = merge(t[p].r, t[q].r, mid + 1, r, 2);
    pushup(p, l, r);
    if (t[t[p].l].rv == lt && t[t[p].r].lv == rt) return p;
    if ((t[t[p].l].rv || t[t[p].r].lv) &&
        (!k || (k == 2 && t[t[p].l].rv < mid - l + 1) || (k == 1 && t[t[p].r].lv < r - mid)))
        Ans::query(1, 1, n, mid - t[t[p].l].rv + 1, mid + t[t[p].r].lv);
    return p;
}
};
inline int read() {
    char c = getchar();
    int x = 0, f = 1;
    while (!isdigit(c)) f = (c == '-' ? -1 : f), c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + c - '0', c = getchar();
    return x * f;
}
inline int find(int x) {
    if (fa[x] == x) return x;
    return fa[x] = find(fa[x]);
}
int i, j, num;
signed main() {
    n = read(), m = read();
    for (i = 1; i <= n; ++i) {
        while (!isdigit(c)) c = getchar();
        while (isdigit(c)) str[++len] = c - '0' + 1, b[len] = i, c = getchar();
        str[++len] = i + 2;
    }
    Sa::n = len, Sa::init();
    for (i = 1; i <= len; ++i){
        if(b[sa[i]]) baf[++num] = make_pair(-ht[i], i), fa[i] = i, rt[i] = Seg::insert(1, n, b[sa[i]]);
        else fa[i] = i - 1;
    } sort(baf + 2, baf + 1 + num);
    for (int i = 1; i <= m; ++i) a[i] = read(), u[a[i]].emplace_back(read(), i);
    Ans::build(1, n);
    for (i = 2; i <= num; ++i) {
        x = baf[i].second, now = -baf[i].first;
        rt[find(x)] = Seg::merge(rt[find(x)], rt[find(x - 1)], 1, n), fa[find(x - 1)] = find(x);
    }
    for (int i = 1; i <= m; ++i) printf("%d\n", ans[i]);
    return 0;
}

以下代码为了可读性和在线,效率较低(此题线段树合并可能只有理论上才优于启发式合并),不开 O2 不保证随时都可以通过。

在线:

#include <algorithm>
#include <cmath>
#include <cstring>
#include <fstream>
#include <iostream>
#include <vector>
#include <cassert>
using namespace std;

// Input
int n, m, len, x, y, sa[500005], ht[500005];
pair<int, int> baf[500005];
int rt[500005], b[500005], fa[500005], now;
char c;
short str[500005];
vector<pair<short, short>> u[20010];
// Sa
namespace Sa {
int n, m, ans, rk[500005], c[500005], x[500005], y[500005];
void init() {
    m = 20002;
    for (int i = 1; i <= n; ++i) ++c[x[i] = str[i]];
    for (int i = 2; i <= m; ++i) c[i] += c[i - 1];
    for (int i = n; i >= 1; --i) sa[c[x[i]]--] = i;
    for (int k = 1; k <= n; k <<= 1) {
        int num = 0;
        for (int i = n - k + 1; i <= n; ++i) y[++num] = i;
        for (int i = 1; i <= n; ++i)
            if (sa[i] > k) y[++num] = sa[i] - k;
        for (int i = 1; i <= m; ++i) c[i] = 0;
        for (int i = 1; i <= n; ++i) ++c[x[i]];
        for (int i = 2; i <= m; ++i) c[i] += c[i - 1];
        for (int i = n; i >= 1; --i) sa[c[x[y[i]]]--] = y[i], y[i] = 0;
        swap(x, y);
        x[sa[1]] = 1;
        num = 1;
        for (int i = 2; i <= n; ++i)
            x[sa[i]] = (y[sa[i]] == y[sa[i - 1]] && y[sa[i] + k] == y[sa[i - 1] + k]) ? num : ++num;
        if (num >= n) break;
        m = num;
    }
    int k = 0;
    for (int i = 1; i <= n; ++i) rk[sa[i]] = i;
    for (int i = 1; i <= n; ++i) {
        if (rk[i] == 1) continue;
        if (k) --k;
        int j = sa[rk[i] - 1];
        while (j + k <= n && i + k <= n && str[i + k] == str[j + k]) ++k;
        ht[rk[i]] = k;
    }
    // for (int i = 1; i <= n; ++i) cout << sa[i] << " ";
    // cout << endl;
    // for (int i = 1; i <= n; ++i) cout << ht[i] << " ";
    // cout << endl;
}
}
namespace Ans {
int tot, rt[20010];
struct Node {
    int l, r, x;
} t[200010];
inline int insert(int p, int l, int r, int i, int x) {
    int tp = ++tot;
    t[tp] = t[p], t[tp].x = max(t[tp].x, x);
    if (l ^ r) {
        int mid = (l + r) >> 1;
        if (i <= mid)
            t[tp].l = insert(t[p].l, l, mid, i, x);
        else
            t[tp].r = insert(t[p].r, mid + 1, r, i, x);
    }
    return tp;
}
inline int ask(int p, int pl, int pr, int l) {
    if (pl >= l) return t[p].x;
    int mid = (pl + pr) >> 1, ans = 0;
    if (l <= mid) ans = max(ans, ask(t[p].l, pl, mid, l));
    return max(ans, ask(t[p].r, mid + 1, pr, l));
}
}
// Segment Tree
namespace Seg {
struct Node {
    int l, r;
    short lv, rv, lt, rt;
} t[8000001];
int tot, bk[8000001], top;
inline void pushup(int p, int l, int r) {
    int mid = (l + r) >> 1;
    if (t[p].l) t[p].lv = t[t[p].l].lv;
    if (t[p].r) t[p].rv = t[t[p].r].rv;
    if (t[t[p].l].lv == mid - l + 1) t[p].lv += t[t[p].r].lv;
    if (t[t[p].r].rv == r - mid) t[p].rv += t[t[p].l].rv;
}
inline int insert(int l, int r, int i) {
    int p = ++tot;
    if (l == r)
        t[p].lv = t[p].rv = 1;
    else {
        int mid = (l + r) >> 1;
        if (i <= mid)
            t[p].l = insert(l, mid, i), t[p].lv = t[t[p].l].lv;
        else
            t[p].r = insert(mid + 1, r, i), t[p].rv = t[t[p].r].rv;
    }
    return p;
}
inline int merge(int p, int q, int l, int r, int k = 0) {
    if (p == q) return p;
    if (!p || !q) return p ^ q;
    int mid = (l + r) >> 1, lt = t[t[p].l].rv, rt = t[t[p].r].lv;
    t[p].lv = max(t[p].lv, t[q].lv), t[p].rv = max(t[p].rv, t[q].rv);
    t[p].l = merge(t[p].l, t[q].l, l, mid, 1), t[p].r = merge(t[p].r, t[q].r, mid + 1, r, 2);
    pushup(p, l, r);
    t[q] = {}, bk[++top] = q;
    if (t[t[p].l].rv == lt && t[t[p].r].lv == rt) return p;
    if ((t[t[p].l].rv || t[t[p].r].lv) &&
        (!k || (k == 2 && t[t[p].l].rv < mid - l + 1) || (k == 1 && t[t[p].r].lv < r - mid)))
        u[mid - t[t[p].l].rv + 1].emplace_back(mid + t[t[p].r].lv, now);
    return p;
}
};
inline int read() {
    char c = getchar();
    int x = 0, f = 1;
    while (!isdigit(c)) f = (c == '-' ? -1 : f), c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + c - '0', c = getchar();
    return x * f;
}
inline int find(int x) {
    if (fa[x] == x) return x;
    return fa[x] = find(fa[x]);
}
int i, j, num;
signed main() {
    n = read(), m = read();
    for (i = 1; i <= n; ++i) {
        while (!isdigit(c)) c = getchar();
        while (isdigit(c)) str[++len] = c - '0' + 1, b[len] = i, c = getchar();
        str[++len] = i + 2;
    }
    Sa::n = len, Sa::init();
    for (i = 1; i <= len; ++i){
        if(b[sa[i]]) baf[++num] = make_pair(-ht[i], i), fa[i] = i, rt[i] = Seg::insert(1, n, b[sa[i]]);
        else fa[i] = i - 1;
    } sort(baf + 2, baf + 1 + num);
    for (i = 2; i <= num; ++i) {
        x = baf[i].second, now = -baf[i].first;
        // j = b[sa[x - 1]] ? x - 1 : x - 2;
        rt[find(x)] = Seg::merge(rt[find(x)], rt[find(x - 1)], 1, n), fa[find(x - 1)] = find(x);
    }
    for (i = 1; i <= n; ++i) {
        Ans::rt[i] = Ans::rt[i - 1];
        for (j = 0; j < u[i].size(); j++)
            Ans::rt[i] = Ans::insert(Ans::rt[i], 1, n, u[i][j].first, u[i][j].second);
    }
    while (m--) {
        x = read(), y = read();
        printf("%d\n", Ans::ask(Ans::rt[x], 1, n, y));
    }
    return 0;
}

标签:rv,int,题解,mid,lv,区间,Luogu,P5576,include
From: https://www.cnblogs.com/Muelsyse/p/17385962.html

相关文章

  • ABC262Ex Max Limited Sequence 题解
    题意:给定\(m\)个限制\((l_i,r_i,p_i)\)及\(n,k\),求满足以下条件的长度为\(n\)的不同序列\(a=(a_1,a_2,\cdots,a_n)\)的数目。\(\foralli\in[1,n],0\leqa_i\leqk\)\(\foralli\in[1,m],\max\limits_{j\in[l_i,r_i]}a_j=p_i\)同P4229,但数据更强,目测只允......
  • 【问题解决】Kafka报错 Bootstrap broker x.x.x.x:9092 (id: -1 rack: null) disconne
    问题复现近日针对某一客户需求开发了一个需要使用Kafka的功能,功能是什么暂且不论,在本地虚机的Kafka连接一切正常遂放到测试服务器上验证功能,以下是监听topic成功和警告报错:2023-05-0910:22:23[localhost-startStop-1]INFOorg.apache.kafka.clients.consumer.ConsumerConfig......
  • ABC191F 题解
    题目传送门题目分析我们发现,\(\text{min}\)操作实际上就是把两数当中较大的那个删除,较小的那个数不受影响,所以用最小的数删还是用另一个数删是无区别的。一个性质:\[\gcd(x,y)\le\min(x,y)\]不管\(a_{min}\)是原来的还是在\(\text{gcd}\)操作后新生成的,所以无论什么时......
  • ant-select数据会发生卡顿问题解决
    <a-selectv-model="form.region"show-searchplaceholder="请选择"option-filter-prop="children"@search="handleSearch"@popupScroll="handlePopu......
  • [AtCoder-AT_ABC070C]题解(C++)
    PartIPreface原题目(Luogu)原题目(AtCoder)PartIISketch给定一个正整数\(N(1\leqN\leq100)\),表示时钟数量。接下来\(N\)行,每行一个正整数\(T_i(1\leqT_i\leq10^{18})\),表示每个时钟旋转\(T_i\)秒后还会回到原来的地方。求出在多少秒之后,所有时钟还会同时......
  • xshell7 免费版 关闭 弹窗问题解决
    原博客地址:https://www.hao.kim/1175.html使用二进制编辑器winhex进行编辑绿色版下载地址:https://mikemhm.lanzoul.com/i6boy0v2a6pa使用winhex打开xshell.exe文件xshell.exe默认目录"C:\ProgramFiles(x86)\NetSarang\Xshell7\Xshell.exe"查找16进制数值74116A006A0......
  • [AtCoder-AT_ABC070_A]题解(C++)
    PartIPreface原题目(Luogu)原题目(AtCoder)PartIISketch给定一个正整数\(n(100\leqn\leq999)\)。求\(n\)是否是一个回文数,是输出\(\texttt{Yes}\),不是输出\(\texttt{No}\)。PartIIIAnalysisSolve1如果仔细观察的话,应该都能发现,\(n\)一定是一个三位数。......
  • P8714 题解
    洛谷P8714题意自己看(思路分五个小题去考虑。问题A枚举门牌号,看门牌号中有多少个\(2\),统计答案即可。voidsloveA(){//问题Aintsum=0;for(inti=1,j;i<=2020;i++){//枚举门牌号j=i;while(j){//枚举每一位sum+=(j%10......
  • 第十四届蓝桥杯省赛C++ B组(个人经历 + 题解)
    参赛感受这是我第一次参加蓝桥杯的省赛,虽然没什么参赛经验,但是自己做了很多前几届蓝桥杯的题,不得不说,这一届蓝桥杯省赛的难度相较于之前而言还是比较大的。之前很流行蓝桥杯就是暴力杯的说法,但是随着参赛人数的增多,比赛认可度的提升,比赛题目的质量也明显越来越高了。这次省赛涉及......
  • CF920E Connected Components? 题解
    一道线段树优化建图好题(大雾扣掉一些边看起来不好做,我们直接大力加上存在的边,然后跑连通块。对于一个点,如果他被扣掉了\(k\)个邻居,那么没扣掉的那些形成了至多\(k+1\)个连续段,可以用线段树优化建图向每个连续段各用\(\log\)的代价连边。由于总共扣掉了\(m\)条边,所以总共......