ZR620 19省选8 con
ZR620. 【19省选8】con
最近复习SA,来补一下当时的题目QAQ
懒得写题目大意了QAQ
首先,如果我们能够对\(n^2\)级别的子串实现排序的话,每读入一个询问,都可以直接二分找到它在哪个子串(根据子串排序完成后的字符数量前缀和),直接输出就好了
也就是说,为题转化成了对\(n^2\)级别的子串进行排序.
首先暴力排序时间复杂度是\(O(n^3)\),5000的数据范围是不大可能的,我们就要利用神奇的\(height\)数组了
很明显,如果我们不考虑重复字串
\([sa_1,sa_1]\)这个串一定最小,\([sa_1,sa_1 + 1]\)其次,就这么向下推下去
但是有重复子串的话,很明显就不可以这么干了.
那我们就在\(height\)数组上暴力扫一下,将\(i\)后面满足\(height_j >= len\)的子串你部找出来,他们也一定就有我们现在当前在算的这个字串.我们把他打上标记,表示这个串已经被提前算过了,等会算到他时直接跳过就好了.
然后这个题就差不多了
我的错误
1 后缀排序时,每次倍增开始前没有将写m = 0,导致RE
2读入的询问字符会超过int,但是读入使用了int
#include<cstdio>
#include<iostream>
#include<cstring>
#include<cctype>
#include<algorithm>
#include<vector>
#define mk make_pair
#define LL long long
using namespace std;
const int N = 5003;
char s[N];
int x[N],y[N],c[N];
int rk[N],height[N],sa[N];
int sum[N];
LL pre[N * (N - 1) / 2];
int Q,n;
vector < pair<int,int> > G;
inline LL read(){
LL v = 0,c = 1;char ch = getchar();
while(!isdigit(ch)){
if(ch == '-') c = -1;
ch = getchar();
}
while(isdigit(ch)){
v = v * 10 + ch - 48;
ch = getchar();
}
return v * c;
}
inline void SA(){
int m = 128;
for(int i = 1;i <= n;++i) x[i] = s[i],c[x[i]]++;
for(int i = 1;i <= m;++i) c[i] += c[i - 1];
for(int i = n;i >= 1;--i) sa[c[x[i]]--] = i;
m = 0;
for(int w = 1;m < n;w <<= 1){
m = 0;
//cout << w << endl;
for(int i = n - w + 1;i <= n;++i) y[++m] = i;
for(int i = 1;i <= n;++i) if(sa[i] > w) y[++m] = sa[i] - w;
for(int i = 1;i <= m;++i) c[i] = 0;
for(int i = 1;i <= n;++i) c[x[i]]++;
for(int i = 1;i <= m;++i) c[i] += c[i - 1];
for(int i = n;i >= 1;--i) sa[c[x[y[i]]]--] = y[i];
for(int i = 1;i <= n;++i) y[i] = x[i];
x[sa[1]] = m = 1;
for(int i = 2;i <= n;++i){
if(y[sa[i]] == y[sa[i - 1]] && y[sa[i] + w] == y[sa[i - 1] + w]) x[sa[i]] = m;
else x[sa[i]] = ++m;
}
}
for(int i = 1;i <= n;++i) rk[sa[i]] = i;
int k = 0;
for(int i = 1;i <= n;++i){
if(k) k--;
int j = sa[rk[i] - 1];
while(s[i + k] == s[j + k]) k++;
height[rk[i]] = k;
}
}
int main(){
scanf("%s",s + 1);
n = strlen(s + 1);
SA();
for(int i = 1;i <= n;++i){//从小到大依次枚举后缀
int t = sa[i];
for(int S = sum[t] + t;S <= n;++S){//sum[i]表示以i开头的前sum[i]个串已经算过了,所以直接从sum[t]+t开始
sum[t]++;G.push_back(mk(t,S));//G两个参数分别为左端点和右端点
for(int j = i + 1;j <= n && height[j] >= S - t + 1;++j){//枚举他后面的所有具有这个子串的串
sum[sa[j]]++;
G.push_back(mk(sa[j],sa[j] + S - t));
}
}
}
int size = G.size();
// for(int i = 0;i < size;++i) printf("%d %d\n",G[i].first,G[i].second);
for(int i = 1;i <= size;++i) pre[i] = pre[i - 1] + 1ll * (G[i - 1].second - G[i - 1].first + 1);
Q = read();
while(Q--){
LL x = read();
int pos = upper_bound(pre + 1,pre + size + 1,x) - pre;
pos--;
x -= pre[pos];
if(x == 0) putchar(s[G[pos - 1].second]);
else putchar(s[G[pos].first + x - 1]);
}
return 0;
}