ZR620 19省选8 con

ZR620. 【19省选8】con

最近复习SA,来补一下当时的题目QAQ

懒得写题目大意了QAQ

AcxsrF.md.png

首先,如果我们能够对\(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;	
}
posted @ 2019-04-03 16:34  wyxdrqcccc  阅读(121)  评论(0编辑  收藏  举报