CF710F String Set Queries AC自动机 二进制分组

题意:

戳这里

分析:

字符串题要求强制在线,进行删除,插入,匹配

这里提供三种做法:

  1. 根号分治

    对于这类字符串匹配问题,我们能直接想到的暴力做法就是建 Trie,或者直接 KMP, 但是 Trie 空间复杂度不太对劲,KMP 时间复杂度不太对劲,那就把两种做法合并一下,对于长度大于 \(\sqrt s\) 的直接暴力 KMP,否则插入 Trie 树中匹配

  2. 根号重构

    除了常见的暴力以外,像这种多文本串的字符串匹配的问题还可以用 AC 自动机,但是尴尬的是 AC 自动机不支持删除,而且每次插入会改变 AC 自动机的形态,需要重新调整 \(fail\) 指针,所以单次插入最劣变成 \(O(n)\) 的,那么怎么办呢?

    对于第一个问题很好解决 ,我们每次删除的时候也建一个关于被删除串的 AC 自动机,这样每一次将两个询问作差得到的就是答案

    对于第二个问题,我们也可以利用上面的根号分治的思想,既然单次插入的复杂度过高,那我们就每 \(\sqrt m\) 个串建一个 AC 自动机每插入 \(\sqrt m\) 个字符串之后就重构两个 AC 自动机,将他们合并

  3. 二进制分组

    其实这个做法和上面的重构差不多,只不过是,把重构的阈值改成了 \(2\) 的幂次,这样大小很小的时候重构次数较多,大小较大的时候重构次数就会变少,比较优秀,因为每个串被重构的次数不会超过 \(O(\log m)\) 次

    tip: Trie 树重构的时候,不能直接在 原来的 Trie 的 ch 数组上建出新的树形,因为可能存在多次重构,所以必须要保留原来的树形,要另外开一个数组表示每一次重构后的 Trie 树

代码:

这里提供的是二进制分组的写法,之前听学姐讲过一遍,今天才算是明白了

#include<bits/stdc++.h>
#define pii pair<int,int>
#define mk(x,y) make_pair(x,y)
#define lc rt<<1
#define rc rt<<1|1
#define pb push_back
#define fir first
#define sec second
#define inl inline
#define reg register

using namespace std;

namespace zzc
{
	typedef long long ll;
	inline int read()
	{
		int x=0,f=1;char ch=getchar();
		while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
		while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
		return x*f;
	}
	
	const int maxn = 3e5+5;
	char s[maxn];
	int m,tot,idx;
	int rt[maxn],siz[maxn],num[maxn],fail[maxn],ch[maxn][26],ch2[maxn][26],pw[maxn];
	
	struct AC
	{
		int st[maxn],top;
		queue<int> q;
		int merge(int x,int y)
		{
			if(!x||!y) return x|y;
			for(int i=0;i<26;i++) ch[x][i]=merge(ch[x][i],ch[y][i]);
			num[x]+=num[y];
			return x;	
		}
		void build(int id)
		{
			for(int i=0;i<26;i++)
			{
				if(ch[rt[id]][i]) fail[ch2[rt[id]][i]=ch[rt[id]][i]]=rt[id],q.push(ch2[rt[id]][i]);
				else ch2[rt[id]][i]=rt[id];
			}
			while(!q.empty())
			{
				int x=q.front();q.pop();
				siz[x]=num[x]+siz[fail[x]];
				for(int i=0;i<26;i++)
				{
					if(ch[x][i])
					{
						fail[ch2[x][i]=ch[x][i]]=ch2[fail[x]][i];
						q.push(ch2[x][i]);
					}
					else ch2[x][i]=ch2[fail[x]][i];
				}
			}
		}
		void insert(char *s)
		{
			int len=strlen(s+1);
			int id=++idx;
			if(!rt[id])rt[id]=++tot;
			int now=rt[id];
			for(int i=1;i<=len;i++)
			{
				int c=s[i]-'a';
				ch[now][c]=++tot;
				now=ch[now][c];
			}
			++num[now];pw[id]++;
			while(top&&pw[st[top]]==pw[id]) rt[id]=merge(rt[id],rt[st[top]]),pw[id]+=pw[st[top--]];
			build(id);
			st[++top]=id;
		}
		int query(char *s)
		{
			int len=strlen(s+1),res=0;
			for(int i=1;i<=top;i++)
			{
				int now=rt[st[i]];
				for(int j=1;j<=len;j++)
				{
					int c=s[j]-'a';
					now=ch2[now][c];
					res+=siz[now];
				}
			}
			return res;
		}
	}f,d;
	
	void work()
	{
		int opt;
		m=read();
		while(m--)
		{
			opt=read();scanf("%s",s+1);
			if(opt==1) f.insert(s);
			else if(opt==2) d.insert(s);
			else printf("%d\n",f.query(s)-d.query(s));
			fflush(stdout);
		}
	}

}

int main()
{
	zzc::work();
	return 0;
}

上一篇:Trie - 字符串统计 & 最大异或合 - AcWing 835 & 256


下一篇:Trie-字典树