zoukankan      html  css  js  c++  java
  • BZOJ 4556: [Tjoi2016&Heoi2016]字符串(后缀数组 + 二分答案 + 主席树 + ST表 or 后缀数组 + 暴力)

    题意

    一个长为 (n) 的字符串 (s),和 (m) 个询问。每次询问有 (4) 个参数分别为 (a,b,c,d)

    要你告诉它 (s[a...b]) 中的所有子串 和 (s[c...d]) 的 最长公共前缀 ((mathrm{LCP})) 的最大值。

    ((1le n,mle 10^5, ale b,cle d,1le a,b,c,dle n))

    题解

    一开始看错了题 以为是 ([a,b]) 中所有子串 和 ([c,d]) 中所有子串的 (mathrm{LCP}) 这怎么能做啊!!!

    仔细观察了一下 发现是 ([a,b]) 的所有子串 和 ([c,d]) 。。。。

    那么题目就变 简单 了一点。。。

    首先我们考虑与 ([c,d]) 有最长 (mathrm{LCP}) 的在哪里

    不难发现 就是后缀排序后 (rk[i])(rk[c]) 最靠近的 (i)

    那么我们可以转化求 ([a,b]) 中的这个 (i) 就行了qwq

    答案表示出来大概是这样子的。

    [displaystyle mathrm{ans}=min(d-c+1,max_{i=a}^{b} {min(mathrm{LCP}(i, c),b-i+1)}) ]

    我们发现 直接求这个 (i) 会被后面的 (b-i+1) 限制掉 所以不能直接这样求

    但我们可以考虑转化一下 我们考虑 二分答案 如果判断一个答案是否存在就容易一些了

    我们考虑二分这个长度 假设是 (len) 那么前面的 (i) 就只能存在于 ([a,b-len+1]) 这个区间内

    然后看 (rk[c]) 周围连续的 (height[q]ge len) 可以延伸到哪个范围 这个东西 直接用 (mathrm{ST}) 可以实现

    怎么实现呢 类似于倍增的思想 就是把那段距离看成一串二进制 然后从高到低去消掉一个个数就行了

    得到这个区间 ([sl,sr]) 后 我们就需要查找里面是否存在 ([a,b-len+1]) 的元素 这个东西就直接上 主席树 就行了

    最后时间复杂度就是 (O(n log^2 n)) 咯(令 (n,q) 同级)

    思路就很清晰了 但是代码就一点也不好写。。。 先挂出来吧。。

    代码

    /**************************************************************
        Problem: 4556
        User: zjp_shadow
        Language: C++
        Result: Time_Limit_Exceed
    ****************************************************************/
     
    #include <bits/stdc++.h>
    #define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
    #define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
    #define Set(a, v) memset(a, v, sizeof(a))
    #define Cpy(a, b) memcpy(a, b, sizeof(a))
    using namespace std;
     
    inline bool chkmin(int &a, int b) {return b < a ? a = b, 1 : 0;}
    inline bool chkmax(int &a, int b) {return b > a ? a = b, 1 : 0;}
     
    inline int read() {
        int x = 0, fh = 1; char ch = getchar();
        for (; !isdigit(ch); ch = getchar()) if (ch == '-') fh = -1;
        for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
        return x * fh;
    }
     
    void File() {
    #ifdef zjp_shadow
        freopen ("4556.in", "r", stdin);
        freopen ("4556.out", "w", stdout);
    #endif
    }
     
    const int N = 2e6 + 1e3;
    struct Suffix_Array {
        int sa[N], tmp[N], rk[N], n, m, c[N];
        char str[N];
     
        void Init(int n, char bas[]) { this -> n = n; Cpy(str, bas); }
     
        inline void Radix_Sort() {
            For (i, 1, m) c[i] = 0;
            For (i, 1, n) ++ c[rk[i]];
            For (i, 1, m) c[i] += c[i - 1];
            Fordown (i, n, 1) sa[c[rk[tmp[i]]] --] = tmp[i];
        }
     
        inline void Build_Sa() {
            For (i, 1, n) rk[i] = str[i], tmp[i] = i;
            m = 255; Radix_Sort();
            for (register int k = 1, p; k <= n; k <<= 1) {
                p = 0;
                For (i, n - k + 1, n) tmp[++ p] = i;
                For (i, 1, n) if (sa[i] > k) tmp[++ p] = sa[i] - k;
                Radix_Sort(); swap(rk, tmp);
                rk[sa[1]] = 1, m = 1;
                For (i, 2, n) rk[sa[i]] = (tmp[sa[i]] == tmp[sa[i - 1]] && tmp[sa[i] + k] == tmp[sa[i - 1] + k]) ? m : ++ m;
                if (m >= n) return ;
            }
        }
     
        int height[N];
        inline void Get_Height() {
            for (int i = 1, j, k = 0; i <= n; ++ i) {
                if (k) -- k;
                j = sa[rk[i] - 1];
                while (str[i + k] == str[j + k]) ++ k;
                height[rk[i]] = k;
            }
        }
    } SA;
     
    struct Chairman_Tree {
        int ls[N], rs[N], tot[N], Size, rt[N];
     
        void Insert(int &o, int pre, int l, int r, int up) {
            o = ++ Size; ls[o] = ls[pre]; rs[o] = rs[pre];
            tot[o] = tot[pre] + 1; if (l == r) return;
            int mid = (l + r) >> 1;
            if (up <= mid) Insert(ls[o], ls[pre], l, mid, up);
            else Insert(rs[o], rs[pre], mid + 1, r, up);
        }
     
        bool Query(int s, int t, int l, int r, int ql, int qr) {
            int now = tot[t] - tot[s];
            if (!now) return false;
            if (ql <= l && r <= qr) return true;
            int mid = (l + r) >> 1;
            if (ql <= mid && Query(ls[s], ls[t], l, mid, ql, qr)) return true;
            if (qr > mid && Query(rs[s], rs[t], mid + 1, r, ql, qr)) return true;
            return false;
        }
    } CT;
     
    struct Sparse_Table {
        int minv[N][20], Log[N];
     
        void Build(int n, int a[]) {
            For (i, 1, n) 
                minv[i][0] = a[i], Log[i] = Log[i >> 1] + 1;
            For (j, 1, Log[n])
                For (i, 1, n - (1 << j) + 1) 
                    minv[i][j] = min(minv[i][j - 1], minv[i + (1 << (j - 1))][j - 1]);
        }
    } ST1, ST2;
     
    int n, m;
     
    inline bool Check(int len, int a, int b, int c, int d) {
        int sl = SA.rk[c], sr = SA.rk[c];
        Fordown (i, ST2.Log[sl], 0) 
            if (ST2.minv[n - sl + 1][i] >= len) sl -= (1 << i);
        Fordown (i, ST1.Log[n - sr + 1], 0) 
            if (ST1.minv[sr + 1][i] >= len) sr += (1 << i);
        int ql = a, qr = b - len + 1;
        return CT.Query(CT.rt[ql - 1], CT.rt[qr], 1, n, sl, sr);
    }
     
    inline int Get_Ans(int a, int b, int c, int d) {
        int l = 1, r = min(b - a + 1, d - c + 1), ans = 0;
        while (l <= r) {
            int mid = (l + r) >> 1;
            if (Check(mid, a, b, c, d)) l = mid + 1, ans = mid;
            else r = mid - 1;
        }
        return ans;
    }
     
    int val1[N], val2[N];
    char str[N];
     
    int main () {
        File();
        n = read(); m = read();
        scanf ("%s", str + 1);
        SA.Init(n, str);
        SA.Build_Sa();
        SA.Get_Height();
     
        For (i, 1, n) {
            CT.Insert(CT.rt[i], CT.rt[i - 1], 1, n, SA.rk[i]);
            val1[i] = val2[n - i + 1] = SA.height[i];
        }
        ST1.Build(n, val1);
        ST2.Build(n, val2);
     
        For (i, 1, m) {
            int a = read(), b = read(), c = read(), d = read();
            printf ("%d
    ", Get_Ans(a, b, c, d));
        }
        //cerr << (double) clock() /CLOCKS_PER_SEC << endl;
        return 0;
    }
    

    彩蛋

    细心的读者肯定发现了 这个代码的 result(mathrm{TLE}) 2333

    为什么呢 本人常数巨大啊!!!

    但这份代码交到 (luogu) 上不开 (O2) 是 8000ms 开了是 4000ms

    我突然想看看别人怎么写的 然后查找一波最优解 诶 有个叫 yyb_test 的神犇 只要 400ms

    我仔细翻阅了一波大佬的代码 惊了 这不是暴力吗!!!

    这才有了我现在的标题 (后缀数组 + 暴力)

    我们继续考虑之前答案的那个式子

    [displaystyle mathrm{ans}=min(d-c+1,max_{i=a}^{b} {min(mathrm{LCP}(i, c),b-i+1)}) ]

    我们考虑向 (rk[c]) 前后去扫一下得到答案

    其中如果此处 (sa[i])([a,b]) 之中的话我们就计入答案就行了。

    然后就有一个史诗级优化 就是当前扫的 (height)(min) 值 如果不优于当前的 (mathrm{ans})

    我们就可以轻易退出循环啦 这由于数据较为随机 所以 (height) 就比较降的比较快 所以就比较快了qwq

    理论 时间复杂度 (O(nq)) 实际(随机数据) 时间复杂度 (O(frac{mathrm{std}}{10})) 23333

    放个对比图2333

    pic

    BruteForce 代码

    /**************************************************************
        Problem: 4556
        User: zjp_shadow
        Language: C++
        Result: Accepted
        Time:1768 ms
        Memory:59916 kb
    ****************************************************************/
     
    #include <bits/stdc++.h>
    #define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
    #define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
    #define Set(a, v) memset(a, v, sizeof(a))
    #define Cpy(a, b) memcpy(a, b, sizeof(a))
    using namespace std;
     
    inline bool chkmin(int &a, int b) {return b < a ? a = b, 1 : 0;}
    inline bool chkmax(int &a, int b) {return b > a ? a = b, 1 : 0;}
     
    inline int read() {
        int x = 0, fh = 1; char ch = getchar();
        for (; !isdigit(ch); ch = getchar()) if (ch == '-') fh = -1;
        for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
        return x * fh;
    }
     
    void File() {
    #ifdef zjp_shadow
        freopen ("4556.in", "r", stdin);
        freopen ("4556.out", "w", stdout);
    #endif
    }
     
    const int N = 2e6 + 1e3;
    struct Suffix_Array {
        int sa[N], tmp[N], rk[N], n, m, c[N];
        char str[N];
     
        void Init(int n, char bas[]) { this -> n = n; Cpy(str, bas); }
     
        inline void Radix_Sort() {
            For (i, 1, m) c[i] = 0;
            For (i, 1, n) ++ c[rk[i]];
            For (i, 1, m) c[i] += c[i - 1];
            Fordown (i, n, 1) sa[c[rk[tmp[i]]] --] = tmp[i];
        }
     
        inline void Build_Sa() {
            For (i, 1, n) rk[i] = str[i], tmp[i] = i;
            m = 255; Radix_Sort();
            for (register int k = 1, p; k <= n; k <<= 1) {
                p = 0;
                For (i, n - k + 1, n) tmp[++ p] = i;
                For (i, 1, n) if (sa[i] > k) tmp[++ p] = sa[i] - k;
                Radix_Sort(); swap(rk, tmp);
                rk[sa[1]] = 1, m = 1;
                For (i, 2, n) rk[sa[i]] = (tmp[sa[i]] == tmp[sa[i - 1]] && tmp[sa[i] + k] == tmp[sa[i - 1] + k]) ? m : ++ m;
                if (m >= n) return ;
            }
        }
     
        int height[N];
        inline void Get_Height() {
            for (int i = 1, j, k = 0; i <= n; ++ i) {
                if (k) -- k;
                j = sa[rk[i] - 1];
                while (str[i + k] == str[j + k]) ++ k;
                height[rk[i]] = k;
            }
        }
    } SA;
     
    int n, m;
    inline int Get_Ans(int a, int b, int c, int d) {
        int ans = 0, len = min(b - a + 1, d - c + 1), tmp = len;
        Fordown (i, SA.rk[c], 1) {
            if (SA.sa[i] >= a && SA.sa[i] <= b)
                chkmax(ans, min(tmp, b - SA.sa[i] + 1));
            chkmin(tmp, SA.height[i]);
            if (tmp <= ans) break;
        }
        tmp = len;
        For (i, SA.rk[c] + 1, n) {
            chkmin(tmp, SA.height[i]);
            if (tmp <= ans) break;
            if (SA.sa[i] >= a && SA.sa[i] <= b)
                chkmax(ans, min(tmp, b - SA.sa[i] + 1));
        }
        return ans;
    }
     
    int val1[N], val2[N];
    char str[N];
    int main () {
        File();
        n = read(); m = read();
        scanf ("%s", str + 1);
        SA.Init(n, str);
        SA.Build_Sa();
        SA.Get_Height();
     
        For (i, 1, m) {
            int a = read(), b = read(), c = read(), d = read();
            printf ("%d
    ", Get_Ans(a, b, c, d));
        }
        //cerr << (double) clock() /CLOCKS_PER_SEC << endl;
        return 0;
    }
    
  • 相关阅读:
    因为这几个TypeScript代码的坏习惯,同事被罚了500块
    如何设计好分布式数据库,这个策略很重要
    线程、多线程和线程池,看完这些你就能全部搞懂了
    章方:征服耶鲁教授的算法大神程序媛
    从零开始学python | 使用Python映射,过滤和缩减函数:所有您需要知道的
    c# 优化代码的一些规则——用委托表示回调[五]
    mysql 重新整理——索引优化explain字段介绍一 [九]
    mysql 重新整理——索引优化explain简单介绍 [八]
    mysql 重新整理——索引简介[七]
    mysql 重新整理——七种连接join连接[六]
  • 原文地址:https://www.cnblogs.com/zjp-shadow/p/8727385.html
Copyright © 2011-2022 走看看