[gym104542F] Interesting String Problem

Since you are a good friend of Jaber and Eyad, they are asking for your help to solve this problem.

You are given a graph consisting of \(n\) nodes, which initially has no edges. For each node \(i\),there's a string \(s_i\)

of lowercase Latin letters written on it.

You have to process \(q\) queries of two types:

  • 1 \(u\) \(v\) : it means add an edge between node uand node v.
  • 2 \(u\) \(t\) : it means for node \(u\) and string \(t\), output the sum of \(cnt_v\) over all nodes \(v\) which belong to the same component as \(u\),where \(cnt_v\) is the number of times \(s_v\) occurs in \(t\) as a substring.

It is guaranteed that the sum of lengths of sv doesn't exceed \(5\times10^5\), and sum of lengths of the query strings doesn't exceed \(5\times10^5\)

1 二进制分组

合并的时候,AC 自动机很难合并,所以考虑定期重构。

对每个点开一个栈,分别表示 \(2^i\) 个串的合并。加入栈时,如果同时存在两个有 \(2^i\) 个串的时候就把他重构成一个 \(2^{i+1}\) 的串。观察到每个串都会被重构 \(\log n\) 次,算上重构,复杂度就 \(O(|S_i|log n|\Sigma|)\)

2.线段树合并。

由于一开始就把所有的串给了出来,所以可以直接给他跑一个 AC 自动机,弄出fail 树。

考虑我后面的询问需要知道什么,需要知道这个点在 fail 树上有多少个祖先是和 \(x\) 在同一个连通块里面的。所以可以用线段树合并去维护这个东西。在第 \(x\) 棵线段树上把 \(dfn_x,dfn_x+sz_x-1\) 这段区间赋值成 \(1\),然后进行线段树合并,单点查询就可以得到答案了。

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5,M=2e5+5;;
int idx,tr[N][26],tme=-1,hd[N],dfn[N],sz[N],u,v,fa[N],op,n,fil[N],q[N],l,r,e_num;
long long ans;
char str[N];
struct edge{
	int v,nxt;
}e[N<<1];
string s[M];
void add_edge(int u,int v)
{
	e[++e_num]=(edge){v,hd[u]};
	hd[u]=e_num;
}
int read()
{
	int s=0;
	char ch=getchar();
	while(ch<'0'||ch>'9')
		ch=getchar();
	while(ch>='0'&&ch<='9')
		s=s*10+ch-48,ch=getchar();
	return s;
}
struct segment{
	int rt[M],tr[N*30],lc[N*30],rc[N*30],idx;
	int merge(int u,int v)
	{
		if(!u||!v)
			return u|v;
		tr[u]+=tr[v];
		lc[u]=merge(lc[u],lc[v]);
		rc[u]=merge(rc[u],rc[v]);
		return u;
	}
	void upd(int&o,int l,int r,int x,int y)
	{
		if(!o)
			o=++idx;
		if(x<=l&&r<=y)
		{
			tr[o]++;
			return;
		}
		int md=l+r>>1;
		if(md>=x)
			upd(lc[o],l,md,x,y);
		if(md<y)
			upd(rc[o],md+1,r,x,y);
	}
	int qry(int&o,int l,int r,int x)
	{
		if(!o)
			return 0;
		if(l==r)
			return tr[o];
		int md=l+r>>1;
		if(md>=x)
			return qry(lc[o],l,md,x)+tr[o];
		return qry(rc[o],md+1,r,x)+tr[o];
	}
	void mge(int x,int y)
	{
		rt[y]=merge(rt[y],rt[x]);
	}
}b;
void insert(string s,int x)
{
	int u=0;
	for(int i=0;i<s.size();i++)
	{
		if(!tr[u][s[i]-'a'])
			tr[u][s[i]-'a']=++idx;
		u=tr[u][s[i]-'a'];
	}
}
void build()
{
	l=1,r=0;
	for(int i=0;i<26;i++)
		if(tr[0][i])
			q[++r]=tr[0][i];
	while(l<=r)
	{
		for(int i=0;i<26;i++)
		{
			if(tr[q[l]][i])
				fil[q[++r]=tr[q[l]][i]]=tr[fil[q[l]]][i];
			else
				tr[q[l]][i]=tr[fil[q[l]]][i];
		}
		++l;
	}
	for(int i=1;i<=idx;i++)
		add_edge(fil[i],i);
}
void sou(int x)
{
	dfn[x]=++tme,sz[x]=1;
	for(int i=hd[x];i;i=e[i].nxt)
		sou(e[i].v),sz[x]+=sz[e[i].v];
}
int find(int x)
{
	if(fa[x]==x)
		return x;
	return fa[x]=find(fa[x]);
}
int main()
{
	n=read();
	for(int i=1;i<=n;i++)
		scanf("%s",str),insert(s[i]=str,fa[i]=i);
	build();
	sou(0);
	for(int i=1;i<=n;i++)
	{
		int u=0;
		for(int j=0;j<s[i].size();j++)
			u=tr[u][s[i][j]-'a'];
		b.upd(b.rt[i],0,idx,dfn[u],dfn[u]+sz[u]-1);
	}
	int q=read();
	while(q--)
	{
		op=read();
		if(op==1)
		{
			u=read(),v=read();
			if(find(u)^find(v))
			{
				b.mge(find(u),find(v));
				fa[find(u)]=find(v);
			}
		}
		else
		{
			ans=0;
			u=read(),scanf("%s",str);
			u=find(u);
			int k=0;
			for(int i=0;str[i];i++)
			{
				k=tr[k][str[i]-'a'];
				ans+=b.qry(b.rt[u],0,idx,dfn[k]);
			}
			printf("%lld\n",ans);
		}
	}
}
  1. Kruskal重构树。

给询问他建一个 kruskal 重构树,然后一次询问在 kruskal 重构树上是一段连续区间 \([l,r]\) 的询问,可以拆成 \(l-1\)\(r\) 的询问,不断给线段树中加入元素,回答询问即可。

posted @ 2023-09-09 16:39  灰鲭鲨  阅读(7)  评论(0编辑  收藏  举报