给定一个字符串 , 次询问,每次给出 和一个字符串 :
要求找到 的所有本质不同子串中第一个字典序大于 的(lower_bound),如果没有,输出 .
保证所有询问的 总长不超过 .
对于字符串 ,它的 只有最多 种可能。
比如对于ab
来说,答案只可能是长度为3的aba,abb,abc,...
,长度为2的ac,ad,ae,...
,长度为1的b,c,d,...
,以及-1
。注意,列出顺序越靠前,答案越优。
我们可以依次枚举每个字符串是否存在,对S建立后缀自动机,问题就转化为:
多次查询,每次问后缀自动机上的一个节点对应的endpos集中是否存在一个给定区间内的数。有两个要点:
- 遍历所有字符串时,使用递归+枚举,可以均摊 从上一个查询节点得到下一个查询节点,详见代码。
- 考虑长度为 的串是否在 内出现时,首先这个字符串必须在后缀自动机中有对应的节点,其次必须有 ,然后节点应该有一个endpos满足
由后缀自动机的性质,节点对应的endpos集是它link树上子树中的所有前缀编号组成的集合。所以问题转化为:
给定一棵树,某些节点上会有唯一且值域在 内的数。若干次询问,问一个节点的子树中是否有区间 内的数。
据某数据结构带师称,可以用dfs序+可持久化权值线段树,然而我不会,qwq。
考虑把所有询问离线,使用树上启发式合并,用set维护每个节点的endpos集,得到每一个节点的endpos集时处理这个节点上的所有询问。
最后再对原来的每组询问遍历所有字符串,出现第一次成功时输出字符串即可。
复杂度分析:
转化后的询问有 个,使用set查询多一个 .
树节点数有 个,set启发式合并 .
将 视作 ,总复杂度为
总结:
- 树上启发式合并可以(暂时性的)得到所有节点的endpos集,适用于离线查询。(按这样的方式是否也可以解决hdu6704?好像可以,但是应该合并能获得rank的平衡树而不是set).
- 不要同时维护两段基本相同的且很长的代码,使用函数将它们合并起来。
- 记录这道题目,当模板用。
/* LittleFall : Hello! */
#include <bits/stdc++.h>
using namespace std; using ll = long long; inline int read();
const int M = 200016, MOD = 1000000007;
//后缀自动机
int sz;
int ch[M][26], len[M], link[M], pre[M];
void extend(const char *s)
{
for(int i=0, lst=0; s[i]; ++i)
{
int c = s[i]-'a', cur = ++sz;
len[cur] = len[lst] + 1;
int p = lst;
while(!ch[p][c]) ch[p][c] = cur, p = link[p];
if(ch[p][c] != cur)
{
int q = ch[p][c];
if(len[p] + 1 == len[q]) link[cur] = q;
else
{
int clone = ++sz;
memcpy(ch[clone], ch[q], sizeof(ch[q]));
link[clone] = link[q];
len[clone] = len[p]+1;
while(ch[p][c]==q) ch[p][c] = clone, p = link[p];
link[q] = link[cur] = clone;
}
}
lst = cur;
pre[cur] = i+1; //前缀节点
}
}
pair<int,int> qrys[M*2*26]; int qcnt; //所有的查询
int qans[M*2*26]; //查询的答案
vector<int> sons[M]; //link树
vector<int> qry[M]; //每个节点对应的查询编号
set<int> st[M]; int tar[M]; //实际目标
void merge(int a, int b) //启发式合并
{
if(st[tar[a]].size()<st[tar[b]].size()) swap(a,b);
for(auto x:st[tar[b]])
st[tar[a]].insert(x);
st[tar[b]].clear();
tar[b] = tar[a];
}
void solve(int u) //dfs解决所有询问
{
for(auto v:sons[u])
solve(v), merge(u, v);
for(auto qid:qry[u])
{
int l = qrys[qid].first, r = qrys[qid].second;
auto it = st[tar[u]].lower_bound(l);
if(it!=st[tar[u]].end() && *it<=r)
qans[qid] = 1;
}
}
char tex[M], pat[M*2]; //读入数据保存
int lef[M], rig[M], pid[M], pln[M], pcnt; //读入数据保存
int solved[M];
void foreach_query(int ftq, int id, int now, int tag) //当前组,下标,节点,操作
{
if(pat[id] && ch[now][pat[id]-'a'])
foreach_query(ftq, id+1, ch[now][pat[id]-'a'], tag);
//printf(" in i=%d j=%d now=%d ch=%c\n",ftq, id-pid[ftq], now,pat[id] );
for(int c=pat[id]?pat[id]+1:'a'; c<='z'; ++c)
{
int tmp = ch[now][c-'a'];
if(tmp && rig[ftq]-lef[ftq]>=id-pid[ftq])
{
//printf("i=%d j=%d c=%c\n",ftq, id-pid[ftq], c );
++qcnt;
if(tag==0) //新建询问
{
qrys[qcnt] = {lef[ftq]+id-pid[ftq], rig[ftq]};
qry[tmp].push_back(qcnt);
}
else if(qans[qcnt]==1 && !solved[ftq]) //检测成功
{
solved[ftq] = 1;
for(int xj=pid[ftq]; xj<id; ++xj)
putchar(pat[xj]);
putchar(c); printf("\n");
}
}
}
}
int main(void)
{
#ifdef _LITTLEFALL_
freopen("in.txt","r",stdin);
#endif
scanf("%s", tex); extend(tex);
for(int i=1; i<=sz; ++i)
sons[link[i]].push_back(i);
for(int i=0; i<=sz; ++i)
{
tar[i] = i;
if(pre[i]) st[i].insert(pre[i]);
}
int q = read();
for(int i=1; i<=q; ++i)
{
lef[i] = read(), rig[i] = read(), pid[i] = pcnt;
scanf("%s", pat+pid[i]); pln[i] = strlen(pat+pid[i]);
pcnt += pln[i]+1;
foreach_query(i, pid[i], 0, 0);
}
solve(0);
qcnt = 0;
for(int i=1; i<=q; ++i)
{
foreach_query(i, pid[i], 0, 1);
if(!solved[i]) printf("-1\n");
}
return 0;
}
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}