【题解】Codeforces1037H. Security 后缀自动机,link树上启发式合并

给定一个字符串 S ( 1 e 5 ) S(1e5) q ( 2 e 5 ) q(2e5) 次询问,每次给出 l , r l,r 和一个字符串 x x

要求找到 s [ l . . . r ] s[l...r] 的所有本质不同子串中第一个字典序大于 x x 的(lower_bound),如果没有,输出 1 -1 .

保证所有询问的 x x 总长不超过 2 e 5 2e5 .


对于字符串 x |x| ,它的 l o w e r _ b o u n d lower\_bound 只有最多 26 x + 26 26|x|+26 种可能。

比如对于ab来说,答案只可能是长度为3的aba,abb,abc,...,长度为2的ac,ad,ae,...,长度为1的b,c,d,...,以及-1。注意,列出顺序越靠前,答案越优。

我们可以依次枚举每个字符串是否存在,对S建立后缀自动机,问题就转化为:

多次查询,每次问后缀自动机上的一个节点对应的endpos集中是否存在一个给定区间内的数。有两个要点:

  1. 遍历所有字符串时,使用递归+枚举,可以均摊 O ( 1 ) O(1) 从上一个查询节点得到下一个查询节点,详见代码。
  2. 考虑长度为 j j 的串是否在 [ l , r ] [l,r] 内出现时,首先这个字符串必须在后缀自动机中有对应的节点,其次必须有 j r l + 1 j\le r-l+1 ,然后节点应该有一个endpos满足 l e f + j 1 e n d p o s r i g lef+j-1\le endpos\le rig

由后缀自动机的性质,节点对应的endpos集是它link树上子树中的所有前缀编号组成的集合。所以问题转化为:

给定一棵树,某些节点上会有唯一且值域在 [ 1 , n ] [1,n] 内的数。若干次询问,问一个节点的子树中是否有区间 [ l , r ] [l,r] 内的数。

据某数据结构带师称,可以用dfs序+可持久化权值线段树,然而我不会,qwq。

考虑把所有询问离线,使用树上启发式合并,用set维护每个节点的endpos集,得到每一个节点的endpos集时处理这个节点上的所有询问。

最后再对原来的每组询问遍历所有字符串,出现第一次成功时输出字符串即可。

复杂度分析:

转化后的询问有 O ( Σ x 26 ) O(\Sigma|x|*26) 个,使用set查询多一个 l o g log .

树节点数有 O ( n ) O(n) 个,set启发式合并 O ( n l o g 2 n ) O(nlog^2n) .

26 26 视作 l o g n logn ,总复杂度为 O ( n l o g 2 n ) O(nlog^2n)


总结:

  1. 树上启发式合并可以(暂时性的)得到所有节点的endpos集,适用于离线查询。(按这样的方式是否也可以解决hdu6704?好像可以,但是应该合并能获得rank的平衡树而不是set).
  2. 不要同时维护两段基本相同的且很长的代码,使用函数将它们合并起来。
  3. 记录这道题目,当模板用。
/* 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;
}
发布了375 篇原创文章 · 获赞 305 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/m0_37809890/article/details/103021224
今日推荐