后缀数组
- 基于倍增和基数排序的思路求出字符串每个后缀的字典序列rank
性质
- 思路:对所有可能的字串进行排序,并不需要一些DP或贪心!
- 对于本道题可以多写一些字符串以发现一些规律。
- 设原始字符串为 s 0 s 1 ⋯ s n s_0s_1 \cdots s_n s0s1⋯sn
- 关键步骤:
设选用
s
i
s_i
si作为逐个异或的字符串,则最终字符串变为
s
0
⊕
i
,
s
1
⊕
i
,
⋯
s
n
⊕
i
s_{0 \oplus i},s_{1 \oplus i},\cdots s_{n \oplus i}
s0⊕i,s1⊕i,⋯sn⊕i
考察长度为
2
k
2^k
2k的字符串前缀
s
0
⊕
i
,
⋯
s
2
k
−
1
−
1
⊕
i
s
2
k
−
1
⊕
i
⋯
s
2
k
−
1
⊕
i
s_{0 \oplus i},\cdots s_{2^{k-1} -1\oplus i} s_{2^{k-1} \oplus i} \cdots s_{2^{k}-1 \oplus i}
s0⊕i,⋯s2k−1−1⊕is2k−1⊕i⋯s2k−1⊕i
后半部分
s
2
k
−
1
⊕
i
⋯
s
2
k
−
1
⊕
i
s_{2^{k-1} \oplus i} \cdots s_{2^{k}-1 \oplus i}
s2k−1⊕i⋯s2k−1⊕i
相当于原始字符串与
s
2
k
−
1
⊕
i
s_{2^{k-1} \oplus i}
s2k−1⊕i异或后的长度为
2
k
−
1
2^{k-1}
2k−1前缀。
- 最后,利用后缀数组(实质上是基数排序)倍增的思路,维护所有可能的字符串2的幂次的前缀即可。
#include<iostream>
#include<vector>
#include<string>
#include<set>
#include<algorithm>
#include<map>
#include<queue>
#include <chrono>
#include<math.h>
#include<unordered_map>
#include <bits/stdc++.h>
using namespace std;
using namespace std;
const int N = 1e5+5;
const int S = 500;
const long long mod = 1e9+7;
typedef long long ll;
int main()
{
int n;cin>>n;
string s;
cin >> s;
int v[1<<n];
int a[1<<n];
for(int i = 0;i<1<<n;i++) a[i]=i;
sort(a,a+(1<<n),[&](int i,int j){return s[0^i]<s[0^j];});
//for(int i = 0;i<1<<n;i++) cout << a[i] << endl;
//该排名需要注意字符相等的情况
int pre = 1;
for(int i = 0;i<1<<n;i++)
{
if(i==0) v[a[i]]=pre;
else
{
if(s[a[i]]==s[a[i-1]]) v[a[i]]=pre;
else v[a[i]]=++pre;
}
}
//for(int i = 0;i<1<<n;i++) cout << v[i] << endl;
for(int i = 1;i<=n;i++) //将幂次从1增加到n
{
//for(int j = 0;j<1<<n;j++) a[j]=j;
sort(a,a+(1<<n),[&](int p,int q){return make_pair(v[p],v[p ^ (1 << (i-1))]) < make_pair(v[q],v[q ^ (1 << (i-1))]);});
pre = 1;
int v_pre[1<<n]; memcpy(v_pre,v,(1<<n)*sizeof(int));
for(int j = 0;j<1<<n;j++)
{
if(j == 0) v[a[j]]=j+1;
else
{
if(make_pair(v_pre[a[j]],v_pre[a[j] ^ (1 << (i-1))]) == make_pair(v_pre[a[j-1]],v_pre[a[j-1] ^ (1 << (i-1))]))
v[a[j]]=pre;
else v[a[j]]=++pre;
}
}
}
int index = a[0];
for(int i = 0;i<s.size();i++) cout<<s[i^index];
system("pause");
return 0;
}
标准答案代码
#include <bits/stdc++.h>
using namespace std;
const int N = 18;
string s;
int a[1<<N], v[1<<N], tmp[1<<N];
int main() {
ios_base::sync_with_stdio(0); cin.tie(0);
int n; cin >> n >> s;
assert(s.size() == (1<<n));
iota(a, a+(1<<n), 0);
sort(a, a+(1<<n), [&](int i, int j){ return s[i] < s[j]; });
for (int i = 1; i < 1<<n; i++)
v[a[i]] = v[a[i-1]] + (s[a[i]] != s[a[i-1]] ? 1 : 0);
for (int k = 1; k < 1<<n; k <<= 1) {
auto cmp = [&](int i, int j){
return v[i] == v[j] ? v[i^k] < v[j^k] : v[i] < v[j];
};
sort(a, a+(1<<n), cmp);
for (int i = 1; i < 1<<n; i++)
tmp[a[i]] = tmp[a[i-1]] + (cmp(a[i-1], a[i]) ? 1 : 0);
copy(tmp, tmp+(1<<n), v);
}
for (int i = 0; i < 1<<n; i++)
cout << s[i^a[0]];
cout << "\n";
}
代码分析
- 注意cmp的写法
- 关于每次循环中,a是否需要重新初始化?
- 似乎需要
- 但做与不做均能AC
- 使用数学归纳法可以证明。事实上,每次根据v对a排序时,只会动上一次结果v相同的组别中的a。初始化a其实增加了一些交换次数,是没有必要的