[SDOI2016]生成魔咒

[SDOI2016]生成魔咒

https://ac.nowcoder.com/acm/problem/20388

后缀数组、set、离散

题解:

图片说明

分析:

首先让我们看看这一题要求的是什么。每一个索引到最左端能构成的魔咒的数量。
看到这里我们应该能反应过来。抓住不变的东西,最左端是始终不变的。
如果我们把字符串reverse一下,那么求的就是每一个后缀的魔咒数量了!!!
很明显的线索

那么就让我们来思考一下反转后字符串的后缀i与后缀i-1吧。
我们清楚,在后缀i-1中的子串也一定在后缀i中!
那么我们要求后缀i中没有的(子串)。
这些子串一定都是以s[n-i-1](就是后缀的开头)为最左端的。废话
以i为最右端的字串一共有i个候选,我们要知道有多少个在后缀i-1中已经出现过了。
其实就是后缀i与后缀1,2,3,,,,i-1的最大的最长公共前缀长度!!!!
对吧。并不是难理解的。如果没能理解建议画图按步骤梳理,讲的有点不好。

但是我们却发现,这里是很耗时的!!

我们绝对不能一一枚举后缀1,2,3,4,,,,,i-1。虽然我们可以利用st表实现常数查询但是也伤不起。
那么我们就想想吧,该怎么办?
从最长公共前缀的求法出发,我们想想解决对策。
后缀i的排名rki
那么对于后缀1到i-1他们的排名rk1到rki-1
只有两种可能,一个是排名比rki高一个是排名比rki低废话
再回想一下如何求最长公共前缀长度,两端排名区间的最小值。
我们要求这些区间最小值的最大值。那么就很明显了,我们只要保留离rki最近的两个排名
一个大于rki记为rkl一个小于rki记为rkr然后我们查询一下[rkl+1,rki]与 [rki+1,rkr]就能找到最大值了。
为什么这样呢?因为对于其他的所有排名大于rki的后缀求最长公共后缀时一定要经过rkl
同理对于其他排名小于rki的后最求最长公共后缀时一定要经过rkr
那么他们与后缀i的最长公共前缀相较于rkl与rkr只会只小不增。毕竟求的是最小值嘛。
那么得解。
具体我是利用set进行的操作。在维护有序的同时,使用二分查找确定rkl与rkr。
注意set中使用二分查找只能使用set方法,stl中的二分查找函数会退化致O(n)级别

另外因为给的数据中 x的大小在[1,1e9]之间,所以我们还要离散一下
##代码如下:

#include<iostream>
#include<algorithm>
#include<set>
#include<map>
using namespace std;
#define re register
const int max_n = 1e5 + 100;
typedef long long ll;
inline int read() {
    char c = getchar();
    int x = 0, f = 1;
    while (!isdigit(c)) { if (c == '-')f = -1;c = getchar(); }
    while (isdigit(c))x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    return x * f;
}
inline void write(int x) {
    int num = 0;char c[15];
    while (x)c[++num] = (x % 10) + 48, x /= 10;
    while (num)putchar(c[num--]);
    putchar('\n');
}
int ranks[max_n], SA[max_n], height[max_n];
int wa[max_n], wb[max_n], wvarr[max_n], wsarr[max_n];
inline int cmp(int* r, int a, int b, int l) {
    return r[a] == r[b] && r[a + l] == r[b + l];
}
inline void get_sa(int* r, int* sa, int n, int m) {
    ++n;
    re int i, j, p, * x = wa, * y = wb, * t;
    for (i = 0; i < m; ++i) wsarr[i] = 0;
    for (i = 0; i < n; ++i) wsarr[x[i] = r[i]]++;
    for (i = 1; i < m; ++i) wsarr[i] += wsarr[i - 1];
    for (i = n - 1; i >= 0; --i) sa[--wsarr[x[i]]] = i;
    for (j = 1, p = 1; p < n; j <<= 1, m = p) {
        for (p = 0, i = n - j; i < n; ++i) y[p++] = i;
        for (i = 0; i < n; ++i) if (sa[i] >= j) y[p++] = sa[i] - j;
        for (i = 0; i < n; ++i) wvarr[i] = x[y[i]];
        for (i = 0; i < m; ++i) wsarr[i] = 0;
        for (i = 0; i < n; ++i) wsarr[wvarr[i]]++;
        for (i = 1; i < m; ++i) wsarr[i] += wsarr[i - 1];
        for (i = n - 1; i >= 0; --i) sa[--wsarr[wvarr[i]]] = y[i];
        for (t = x, x = y, y = t, p = 1, x[sa[0]] = 0, i = 1; i < n; ++i)
            x[sa[i]] = cmp(y, sa[i - 1], sa[i], j) ? p - 1 : p++;
    }
}//求解高度数组,height[i]指排名
inline void get_height(int* r, int* sa, int n) {
    re int i, j, k = 0;
    for (i = 1; i <= n; ++i) ranks[sa[i]] = i;
    for (i = 0; i < n; height[ranks[i++]] = k)
        for (k ? k-- : 0, j = sa[ranks[i] - 1]; r[i + k] == r[j + k]; k++);
    return;
}
int st[max_n][32];
void initSt(int a[], int n) {
    for (int i = 0;i <= n;++i)st[i][0] = a[i];
    int mxk = (int)log2(n + 1);
    for (int k = 1;k <= mxk;++k) {
        for (int i = 0;i <= n;++i) {
            if (i + (1 << k) - 1 > n)break;
            st[i][k] = min(st[i][k - 1], st[i + (1 << (k - 1))][k - 1]);
        }
    }
}
int que(int l, int r) {
    int len = log2(r - l + 1);
    return min(st[l][len], st[r - (1 << len) + 1][len]);
}
int a[max_n];
int b[max_n];

int main() {
    ios::sync_with_stdio(0);
    int n = read();
    map<int, int> mp;
    for (re int i = n - 1;i >= 0;--i)a[i] = read();
    for (re int i = 0;i < n;++i)b[i] = a[i];
    sort(b, b + n);
    int tot = 0;
    for (re int i = 0;i < n;++i)if (!mp[b[i]])mp[b[i]] = ++tot;
    for (re int i = 0;i < n;++i)a[i] = mp[a[i]];
    get_sa(a, SA, n, tot + 2);
    get_height(a, SA, n);
    initSt(height, n);
    set<int> myset1;
    set<int> myset2;
    set<int>::iterator it;
    ll ans = 0;
    for (re int i = n - 1;i >= 0;--i) {
        int rk = ranks[i];
        int len = 0;
        it = myset1.upper_bound(rk);
        if (it != myset1.end())
            len = max(len, que(rk + 1, *it));
        it = myset2.upper_bound(-rk);
        if (it != myset2.end())
            len = max(len, que(-(*it) + 1, rk));
        myset1.insert(rk);
        myset2.insert(-rk);
        ans += n - i - len;
        write(ans);
    }
}
全部评论

相关推荐

10-25 00:32
香梨想要offer:感觉考研以后好好学 后面能乱杀,目前这简历有点难
点赞 评论 收藏
分享
像好涩一样好学:这公司我也拿过 基本明确周六加班 工资还凑活 另外下次镜头往上点儿
点赞 评论 收藏
分享
头像
11-27 14:28
长沙理工大学
刷算法真的是提升代码能力最快的方法吗?&nbsp;刷算法真的是提升代码能力最快的方法吗?
牛牛不会牛泪:看你想提升什么,代码能力太宽泛了,是想提升算法能力还是工程能力? 工程能力做项目找实习,算法也分数据结构算法题和深度学习之类算法
点赞 评论 收藏
分享
评论
1
收藏
分享
牛客网
牛客企业服务