【牛客2020多校训练营第一场A题】B-Suffix Array 结论 or 思路 + 后缀数组
题意
给出一个字符串 s ,它的以下标 i 为开头的后缀为 \(s_i\) ,给出 B 函数,对每个后缀进行 B 函数的运算。定义 B 函数如下:
对于一个字符串 t ,根据以下运算得到其 b 数组:
\(b_i\ =\ min((i-j)_{t_j\ =\ t_i,j < i})\)
\(b_i = 0\)
对 s 的每个后缀做完 B 函数运算之后,根据每个后缀的 b 数组,按照字典序进行升序排序,依次输出。
题解1
官方的题解是个结论,贴个图:
可参考此博客2020牛客多校第一场 B题Suffix Array(结论+后缀数组)
代码
#include <bits/stdc++.h>
#define pb push_back
const int N = 1e5 + 10;
const int inf = 0x3f3f3f3f;
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
int sa[N], rk[N], oldrk[N], cnt[N], pos[N], valk[N],la[2];
char str[N];
int cmp(int x, int y, int k)
{
return oldrk[x] == oldrk[y] && oldrk[x + k] == oldrk[y + k];
}
void getsa(int *s,int n)
{
int m=n;
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;i++) ++cnt[rk[i]=s[i]];
for (int i = 1; i <= m; i++)
cnt[i] += cnt[i - 1];
for (int i = n; i; i--)
sa[cnt[rk[i]]--] = i;
for (int k = 1; k <= n; k *= 2)
{
int num = 0;
for (int i = n - k + 1; i <= n; i++)
pos[++num] = i;
for (int i = 1; i <= n; i++)
{
if (sa[i] > k)
pos[++num] = sa[i] - k;
}
for(int i=1;i<=m;i++) cnt[i]=0;
//memset(cnt,0,sizeof(cnt));
for (int i = 1; i <= n; i++)
++cnt[rk[i]];
for (int i = 1; i <= m; i++)
cnt[i] += cnt[i - 1];
for (int i = n; i; i--)
sa[cnt[rk[pos[i]]]--] = pos[i];
num = 0;
for(int i=1;i<=n;i++) oldrk[i]=rk[i];
// memcpy(oldrk, rk, sizeof(rk));
for (int i = 1; i <= n; i++)
rk[sa[i]] = cmp(sa[i], sa[i - 1], k) ? num : ++num;
m = num;
if (num == n)
break;
}
for (int i = 1; i <= n; i++)
rk[sa[i]] = i;
}
int main()
{
int n;
while (~scanf("%d", &n))
{
scanf("%s",str+1);
la[0]=la[1]=n+1;
for(int i=n;i;i--)
{
if(la[str[i]-'a']==n+1) valk[i]=n;
else valk[i]=la[str[i]-'a']-i;
la[str[i]-'a']=i;
}
valk[n+1]=n+1;
getsa(valk,n+1);
for(int i=n;i;i--)
printf("%d ",sa[i]);
printf("\n");
}
return 0;
}
/*
*/
题解2
看了一位博主的思路:2020年牛客多校A题_weixin_43965698的博客-CSDN博客
其实如果 \(b_i=min((j-i)_{t_j=t_i,j>i})\) ,那么我们就可以直接使用后缀数组排序。
但是这题写一个样例其实还是可以发现后缀数组的痕迹。
套用博主的图:
将每个后缀的 b 数组写出来之后还是可以发现,D部分其实存在前后缀的关系。
A部分根据字符 a 和字符 b 出现的出现的位置即可得出,并且可以根据它的长度进行排序。
首先根据A部分进行排序,如果A部分相同,那么我们再按照 D 部分排序,我们可以知道 D 部分是s 转化出的 b 数组的后缀,所以我们直接对 b 进行后缀数组排序,比较的时候直接比较排名即可。
注意:根据 A 的长度排序其实存在一些错误,比如 baa 这个样例。
所以对于只有 a 或者 b 的后缀,我们要在最后加上一个 a 或者 b。
这样对于结果并不会造成影响,可以自己模拟一下。
代码中有些注释:
代码
#include <bits/stdc++.h>
#define emplace_back push_back
#define pb push_back
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const int N = 1e5 + 10;
char str[N];
int sa[N], rk[N], oldrk[N], pos[N], cnt[N];
int cmp(int x, int y, int k)
{
return oldrk[x] == oldrk[y] && oldrk[x + k] == oldrk[y + k];
}
void getsa(int *s, int n)
{
int m = n;
memset(cnt, 0, sizeof(cnt));
for (int i = 1; i <= n; i++)
++cnt[rk[i] = s[i]];
for (int i = 1; i <= m; i++)
cnt[i] += cnt[i - 1];
for (int i = n; i; i--)
sa[cnt[rk[i]]--] = i;
for (int k = 1; k <= n; k *= 2)
{
int num = 0;
for (int i = n - k + 1; i <= n; i++)
pos[++num] = i;
for (int i = 1; i <= n; i++)
{
if (sa[i] > k)
pos[++num] = sa[i] - k;
}
for(int i = 0; i <= m; i++)
cnt[i] = 0;
for (int i = 1; i <= n; i++)
++cnt[rk[i]];
for (int i = 1; i <= m; i++)
cnt[i] += cnt[i - 1];
for (int i = n; i; i--)
sa[cnt[rk[pos[i]]]--] = pos[i];
num = 0;
for(int i = 1; i <= n; i++)
oldrk[i] = rk[i];
for(int i = 1; i <= n; i++)
rk[sa[i]] = cmp(sa[i], sa[i-1], k) ? num : ++num;
if(num == n)
break;
m = num;
}
for (int i = 1; i <= n; i++)
rk[sa[i]] = i;
}
int len[N], b[N], ans[N];//len[i]表示后缀i的A部分长度,b数组是后缀1的b数组
int cmp2(int a, int b)
{
if(len[a] == len[b])//A部分相同,按照D部分的排名排序
return rk[a + len[a]] < rk[b + len[b]];
return len[a] < len[b];
}
int main()
{
// freopen("D:\\1.in","r",stdin);
// freopen("D:\\my.out","w",stdout);
int n;
while (~scanf("%d%s", &n, str + 1))
{
int lst[2];
lst[0] = lst[1] = n + 1;//先在最后加上a和b
for (int i = n; i; i--)
{
if(str[i] == 'a')
len[i] = lst[1] - i + 1;
else
len[i] = lst[0] - i + 1;
lst[str[i] - 'a'] = i;
}
lst[0] = lst[1] = 0;
for (int i = 1; i <= n; i++)
{
ans[i] = i;
if (lst[str[i] - 'a'])
b[i] = i - lst[str[i] - 'a'] + 1;
else
b[i] = 1;
lst[str[i] - 'a'] = i;
}
getsa(b, n);
rk[n + 1] = -1;//最后要加上两个,参考样例ab
rk[n + 2] = -2;
sort(ans + 1, ans + 1 + n, cmp2);
for(int i = 1; i <= n; i++)
printf("%d ",ans[i]);
printf("\n");
}
return 0;
}
/*
babaa
abbaa
ababa
bbaba
baabb
*/