2020牛客暑期多校训练营(第一场)A B-Suffix Array(后缀数组,思维)

链接:https://ac.nowcoder.com/acm/contest/5666/A
来源:牛客网
 

题目描述

The BBB-function B(t1t2…tk)=b1b2…bkB(t_1 t_2 \dots t_k) = b_1 b_2 \dots b_kB(t1​t2​…tk​)=b1​b2​…bk​ of a string t1t2…tkt_1 t_2 \dots t_kt1​t2​…tk​ is defined as follows.

  • If there is an index j<ij < ij<i where tj=tit_j = t_itj​=ti​, bi=min⁡1≤j<i,tj=ti{i−j}b_i = \min_{1 \leq j < i, t_j = t_i}\{i - j\}bi​=min1≤j<i,tj​=ti​​{i−j},
  • Otherwise, bi=0b_i = 0bi​=0.

Given a string s1s2…sns_1 s_2 \dots s_ns1​s2​…sn​, sort its nnn suffixes into increasing lexicographically order of the BBB-function.

Formally, the task is to find a permutaion p1,p2,…,pnp_1, p_2, \dots, p_np1​,p2​,…,pn​ of {1,2,…,n}\{1, 2, \dots, n\}{1,2,…,n} such that B(spi−1…sn)<B(spi…sn)B(s_{p_{i - 1}} \dots s_n) < B(s_{p_{i}} \dots s_n)B(spi−1​​…sn​)<B(spi​​…sn​) holds for i=2,…,ni = 2, \dots, ni=2,…,n.

输入描述:

 

The input consists of several test cases terminated by end-of-file.

The first line of each test case contains an integer n.
The second line contains a string s1s2…sns_1 s_2 \dots s_ns1​s2​…sn​.

* 1≤n≤1051 \leq n \leq 10^{5}1≤n≤105
* sis_isi​ is either '`a`' or '`b`'.
* The sum of n does not exceed 10610^{6}106.

输出描述:

For each test case, print n integers which denote the result.

示例1

输入

复制2 aa 3 aba 5 abaab

2
aa
3
aba
5
abaab

输出

复制2 1 3 2 1 5 4 2 1 3

2 1
3 2 1
5 4 2 1 3

备注:

For s = aba, 
* B(s1s2s3)=(0,0,2)B(s_1 s_2 s_3) = (0, 0, 2)B(s1​s2​s3​)=(0,0,2)
* B(s2s3)=(0,0)B(s_2 s_3) = (0, 0)B(s2​s3​)=(0,0)
* B(s3)=(0)B(s_3) = (0)B(s3​)=(0)
Therefore, B(s3)<B(s2s3)<B(s1s2s3)B(s_3) < B(s_2 s_3) < B(s_1 s_2 s_3)B(s3​)<B(s2​s3​)<B(s1​s2​s3​).

题意不再赘述,说下思路。

我们可以得到B(s1...sn)。如果说B(s2...sn),B(s3...sn),B(sn)依此是B(s1..sn)的后缀,显然可以直接用后缀数组得到后缀排名。

但是他们之间可能不是后缀关系,原因在于每个后缀相较于上个后缀第一个a或者b的位置发生了改变。

比如

abaabaab
00213213
 0013213
  010213
   00213
    0013
     010
      00
       0

B(s2...sn)相较于B(s1...sn),第一个b的位置并没有发生改变,但是第一个a的位置改变了,所以发生了改变。

对于某个字符串,第一个a跟第一个b或者第一个b跟第一个a之间组成的B序列一定是0 + (若干个1) + 0形式。

比如B(bbba)=0110,B(ab)=00,B(aab)=010。对于某两个后缀,我们比较他们的B时,首先可以得到第一段比较依据

即第一个a跟第一个b(或者第一个b跟第一个a)组成的B,这个时候由于转化后都是01...10,长度长的一定是字典序大的。

如果相等了,对于两个后缀去掉这一段01...10后,剩余的短的一定是长的那个的后缀,这个时候我们可以通过后缀数组

直接判断排名即可。

比如abaabaab

第一段长度不同,例如比较B(s1...sn)跟B(s3...sn)。

[s1...sn]第一个a跟第一个b的距离是1,这个时候长度为2,即B(s1s2)=00。

[s3...sn]第一个a跟第一个b的距离是2,这个时候长度为3,即B(s3s4s5)=010。

显然后者长度更长,即字典序更大, 直接可以得到B(s3...sn)大于B(s1...sn)。

第一段长度相同时,例如比较B(s1...sn)跟B(s2...sn)。

s1...sn的第一个a的位置在1第一个b的位置在2,所以这一段的B即是00,s2...sn的第一个b的位置在2,第一个a的位置在3.

所以这个长度也是2,即00。这个时候由于长度相同,我们无法直接,对于B(s2...sn) = (0013213)去掉这段00后剩余的B(s4...sn) = (13213)的B序列一定是B(s1...sn) = (00213213)的剩余B(s3...sn) = (213213)的B序列的

后缀,因为在4这个位置之前的a跟b都确定了,从4位置开始两者的B序列是相同的,这时候比较rank[3] > rank[4],所以B(s2...sn)字典序小于B(s1...sn)。同理其他的也是这么比较,直接写一个cmp函数即可再sort即可。

 

这题发现B序列什么时候会改变,以及什么位置开始不变,不变的即为上一次的后缀,可以使用后缀数组排序后直接比较。

从来没见过这么良心的出题人,做法当题目, B-Suffix Array,对B序列建后缀数组。

代码:

还有些细节需要处理,比如某个后缀只含一种字符。

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n, r;
int sa[N];
int rak[N << 1];
int tmp[N << 1];
int cnt[N];
int b[N];
char str[N];
int pos[N];
int ans[N];
int len[N];
int flag[N];
int nxt[N][2];
void radix_sort(int* rank, int* tp)
{
    memset(cnt, 0, (sizeof cnt[0]) * (r + 10));
    for (int i = 1; i <= n; i++)
        cnt[rank[tp[i]]]++;
    for (int i = 1; i <= r; i++)
        cnt[i] += cnt[i - 1];
    for (int i = n; i >= 1; i--)
        sa[cnt[rank[tp[i]]]--] = tp[i];
}
void suffix(int mx)
{
    int *rk = rak, *tp = tmp;
    for (int i = 1; i <= n; i++)
        rk[i] = b[i] + 1, tp[i] = i, tp[i + n] = rk[i + n] = 0;
    r = mx + 1;
    radix_sort(rk, tp);
    for (int p = 0, l = 1, i; p < n; l <<= 1, r = p)
    {
        for (p = 0, i = n - l + 1; i <= n; i++)
            tp[++p] = i;
        for (int i = 1; i <= n; i++)
            if (sa[i] > l)
                p++, tp[p] = sa[i] - l;
        radix_sort(rk, tp);
        swap(rk, tp);
        rk[sa[1]] = 1;
        p = 1;
        for (int i = 2; i <= n; i++)
        {
            if (tp[sa[i - 1]] != tp[sa[i]] || tp[sa[i] + l] != tp[sa[i - 1] + l])
                p++;
            rk[sa[i]] = p;
        }
    }
    for (int i = 1; i <= n; i++)
        rak[sa[i]] = i;
}
bool cmp(int x, int y)
{
    int lenx = len[x], leny = len[y];
    if (lenx != leny)
        return lenx < leny;
    if (flag[x] || flag[y])
        return flag[x] < flag[y];
    return rak[x + lenx + 1] < rak[y + leny + 1];
}
int main()
{
#ifdef LOCAL
    freopen("E:\input.txt", "r", stdin);
#endif // LOCAL
    while (~scanf("%d", &n))
    {
        int u = 0;
        scanf("%s", str + 1);
        pos[0] = pos[1] = 0;
        for (int i = 1; i <= n; i++)
        {
            ans[i] = i;
            int c = str[i] - 'a';
            b[i] = 0;
            if (pos[c])
                b[i] = i - pos[c];
            pos[c] = i;
            u = max(u, b[i]);
        }
        nxt[n + 1][0] = nxt[n + 1][1] = n + 1;
        for (int i = n; i >= 1; i--)
        {
            int c = str[i] - 'a';
            nxt[i][c] = i;
            nxt[i][c ^ 1] = nxt[i + 1][c ^ 1];
            len[i] = -(nxt[i][c] - nxt[i][c ^ 1]);
            if (nxt[i][c ^ 1] == n + 1)
                flag[i] = 1, len[i]--;
        }
        suffix(u);
        sort(ans + 1, ans + n + 1, cmp);
        for (int i = 1; i <= n; i++)
        {
            flag[i] = 0;
            printf("%d%c", ans[i], (i == n ? '\n' : ' '));
        }
    }
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值