hdu-6704
首先给了我们一个字符串,姑且命名为文本串,然后有q个查询,对于每个查询,包含两个数l,r 询问在文本串l到r这段子串在文本串中第k次出现的首字母的位置,不满足输出-1
我们首先可以想到用ac自动机,对所有查询建立一个ac自动机,然后把文本串放上面跑。可是现实打击了我们,范围太大,建字典树就超内存了。于是我们又想到后缀数组也可以处理类似问题,然后对于第k大,我们可以用主席树去维护。(当然后缀自动机也行,篛蒻的我不会)
我们可以对文本串建立后缀数组,用sa数组去建立主席树。然后对与每个查询,我们二分出左右区间(即存在查询子串的排名区间),对这个区间找第k小就行
/*后缀数组+主席树*/
#include
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N = 1e5 + 100;
struct node//静态主席树节点
{
int l, r, val;
}T[N * 50];
int root[N], sa[N], tp[N], rak[N], tax[N], height[N];
/*
sa[i] 表示 排名为i的后缀的首字母位置
tp[i] 第二关键字,性质同sa
rak[i] 表示 第i个后缀的排名
tax[i] 排名为i的数量,桶
height[i] = lcp(sa[i - 1], sa[i]) 排名相邻的两个后缀的最长公共前缀
*/
int dp[N][32], lg2[N];
char s[N];
int n, m, q, cnt;
void Qsort()//基数排序
{
for(int i = 0; i <= m; i ++)
tax[i] = 0;
for(int i = 1; i <= n; i ++)
tax[rak[i]] ++;
for(int i = 1; i <= m; i ++)
tax[i] += tax[i - 1];
for(int i = n; i >= 1; i --)
sa[tax[rak[tp[i]]] --] = tp[i];
}
void get_SA()//获取sa
{
for(int i = 1; i <= n; i ++)
{
rak[i] = (int)s[i];
tp[i] = i;
}
Qsort();
for(int w = 1, p = 0; p < n; m = p, w <<= 1)
{
p = 0;
for(int i = 1; i <= w; i ++)
tp[++ p] = n - w + i;
for(int i = 1; i <= n; i ++)
if(sa[i] > w)
tp[++ p] = sa[i] - w;
Qsort();
swap(rak, tp);
rak[sa[1]] = p = 1;
for(int i = 2; i <= n; i ++)
rak[sa[i]] = (tp[sa[i]] == tp[sa[i - 1]] && tp[sa[i] + w] == tp[sa[i - 1] + w]) ? p : ++ p;
}
}
/*
h[i] = height[rak[i]]
h[i] >= h[i - 1] - 1
*/
void get_HEIGHT()//获取height
{
int k = 0;
for(int i = 1; i <= n; i ++)
{
if(k)
k --;
int j = sa[rak[i] - 1];
while(s[i + k] == s[j + k])
k ++;
height[rak[i]] = k;
}
}
void init()//建立st表,求区间最值
{
for(int i = 1; i <= n; i ++)
dp[i][0] = height[i];
for(int j = 1; (1 << j) <= n; j ++)
for(int i = 1; i + (1 << j) - 1 <= n; i ++)
dp[i][j] = min(dp[i][j - 1], dp[i + (1 << (j - 1))][j - 1]);
}
int rmq(int l, int r)//查询最值
{
if(l == r)
return n;
if(l > r)
swap(l, r);
l ++;//lcp(x, y) = min(lcp(k - 1, k)) (x < k <= y)
int k = lg2[r - l + 1];
return min(dp[l][k], dp[r - (1 << k) + 1][k]);
}
void update(int l, int r, int &x, int y, int pos)//主席树插入点
{
T[++ cnt] = T[y];//将原节点赋值给新节点
T[cnt].val ++;//计数加一
x = cnt;
if(l == r)
return ;
int mid = (l + r) >> 1;
if(mid >= pos)
update(l, mid, T[x].l, T[y].l, pos);
else
update(mid + 1, r, T[x].r, T[y].r, pos);
}
int query(int l, int r, int x, int y, int k)//查询区间第k小
{
if(l == r)
return l;
int mid = (l + r) >> 1;
int tmp = T[T[y].l].val - T[T[x].l].val;
if(tmp >= k)//左边的数量超过了k,说明结果在左子树上
return query(l, mid, T[x].l, T[y].l, k);
else //反之在右子树上
return query(mid + 1, r, T[x].r, T[y].r, k - tmp);
}
int main()
{
ios::sync_with_stdio(false);
int t;
cin >> t;
for(int i = 2; i <= N; i ++)//常数优化
lg2[i] = lg2[i / 2] + 1;
while(t --)
{
cnt = 0;
memset(root, 0, sizeof(root));
memset(T, 0, sizeof(T));
memset(dp, 0x3f, sizeof(dp));
cin >> n >> q;
cin >> (s + 1);
m = (int)'z';
get_SA();
get_HEIGHT();
init();
for(int i = 1; i <= n; i ++)
update(1, n, root[i], root[i - 1], sa[i]);
while(q --)
{
int l, r, k;
cin >> l >> r >> k;
int len = r - l + 1;
int left = 1, right = rak[l];
int ans = rak[l];
while(left <= right)//二分到左边界
{
int mid = (left + right) >> 1;
if(rmq(mid, rak[l]) >= len)
{
ans = mid;
right = mid - 1;
}
else
left = mid + 1;
}
int el = ans;
ans = rak[l];
left = rak[l], right = n;
while(left <= right)//二分到右边界
{
int mid = (left + right) >> 1;
if(rmq(mid, rak[l]) >= len)
{
ans = mid;
left = mid + 1;
}
else
right = mid - 1;
}
int er = ans;
if(er - el + 1 < k) //区间内排名数量不足k个
cout << -1 << endl;
else
cout << query(1, n, root[el - 1], root[er], k) << endl;
}
}
}