P10176 「OICon-02」Native Faith 题解

Sol

由于 \(f(a,b,c)=\sum\limits_{i=1}^{|a|}\sum\limits_{j=i}^{|a|}\sum\limits_{k=1}^{|b|}\sum\limits_{l=k}^{|b|}[a_{i,i+1,\cdots,j}+b_{k,k+1,\cdots,l} = c]\),所以 \(a\) 一定是 \(c\) 的前缀,\(b\) 一定是 \(c\) 的后缀,所以我们对 \(s_k\) 每个前缀和后缀考虑。

如果我们设 \(f_{i}\) 表示 \(s_k\)\(i\) 个字符在 \(s_{l \sim r}\) 的出现次数,\(g_{i}\) 表示 \(s_k\) 从第 \(i\) 个字符开始一直到末尾在 \(s_{l \sim r}\) 的出现次数,那么答案显然是 \(\sum f_i g_{i+1}\) 的。

那我们考虑把 \(f_{i}\)\(g_{i}\) 求出来,单独求这个东西肯定是可以拆一下,变成在 \(s_{1 \sim r}\) 的出现次数减去在 \(s_{1 \sim l-1}\) 的出现次数的。

以下我们只考虑求 \(f_{i}\),因为求 \(g_{i}\) 可以把 \(s_{i}\) 全部取反,是类似的。

你关注一下这个形式,求 \(s_{1 \sim x}\)\(s_k\) 的出现次数,这个东西不是很版吗?先建出 AC 自动机,然后离线下来扫,每次插入一个 \(s_{i}\),就是在 AC 自动机上把根节点到 \(s_i\) 这条路径点权都加 \(1\),查询就是在 fail 树上节点 \(s_k\) 的子树和。

你发现查询的次数特别多,有 \(\sum |s_k|\) 次,然而修改只有 \(\sum |s_i| = m\) 次,所以用分块平衡一下复杂度,让查询复杂度 \(O(1)\),修改复杂度 \(O(\sqrt{m})\) 即可做到 \(O(\sum |s_k| + m \sqrt m)\) 的复杂度。

然而就算这样,\(O(\sum |s_k|) = O(qm)\) 的复杂度仍然接受不了。

我们考虑根号分治,设阀值为 \(B\)

如果 \(|s_k| \le 2B\),是可以按上面部分来做的,时间复杂度 \(O(q B+m \sqrt m)\),空间复杂度 \(O(q B)\)

否则 \(|s_k| > 2B\),我们对于 \(f_i\),我们分成 \(i \le B\)\(B < i < |s_k|-B\)\(|s_k|-B \le i\) 三个部分分别求。

对于 \(i \le B\)\(|s_k|-B \le i\)\(f_i\) 我们依然可以像上面那样求,时间复杂度 \(O(q B + m \sqrt m)\)

\(|s_i| > B\),我们设 \(v_i = 1\),否则 \(v_i = 0\)

对于 \(B < i < |s_k|-B\) 的部分一定满足 \(i>B\),此时这个前缀只可能由 \(v_i=1\) 的字符串 \(s_i\) 作贡献,而这样的字符串 \(s_i\) 只有 \(O(\frac{m}{B})\) 个,而且 \(i < |s_k|-B\),所以后缀也是只有 \(O(\frac{m}{B})\) 个的,又因为 \(|s_k| > 2B\),所以 \(k\) 的个数也只有 \(O(\frac{m}{B})\) 个。

因此假设我们处理第 \(l\)\(v_i = 1\) 的串到第 \(r\)\(v_i = 1\) 的串对 \(k\)\(f\) 的贡献时,\((k,l,r)\) 的个数是 \(O(\frac{m^3}{B^3})\) 级别的。

这个东西我们继续拆成前 \(r\)\(v_i = 1\) 的串的贡献减去前 \(l-1\)\(v_i = 1\) 的串的贡献。

我们设 \(sum_{i,k,j}\) 表示前 \(i\)\(v=1\) 的串,对 \(k\)\(f_j\) 造成了多少贡献,这里使用了 \(O(\frac{m^2}{B})\) 的空间,预处理的时间复杂度也是 \(O(\frac{m^2}{B})\)

求答案时我们求出 \([l,r]\) 对应的 \(v=1\)\([l',r']\),每个 \((k,l',r')\) 我们都只进行一次暴力求解,对于每个不同的位置 \(k\),此时 \(\sum|s_k| = m\),因此对于不同的 \(k\) 只会暴力遍历 \(m\) 遍,又因为不同的 \(l'\)\(r'\) 对相同的 \(k\) 会多次枚举,最多枚举 \(O(\frac{m^2}{B^2})\) 次,故时间复杂度是 \(O(\frac{m^3}{B^2})\) 的,空间复杂度是 \(O(\frac{m^3}{B^3})\) 的。

时间复杂度 \(O(q B+ m \sqrt m + \frac{m^2}{B} + \frac{m^3}{B^2})\),空间复杂度 \(O(qB + \frac{m^2}{B} + \frac{m^3}{B^3})\)

\(n,m,q\) 同阶,取 \(B = n^{\frac{2}{3}}\) 可以达到理论最优复杂度 \(O(n ^ \frac{5}{3})\),实际我的写法 \(B\)\(150\) 左右最快。

实际上让 \(B\) 再变小还可以更快,但再小就爆空间了。

目前是跑到了最优解

Code

#include<bits/extc++.h>
using namespace std;
#define ll long long 

namespace FastIO { // 太长了就省略快读快写
}
#define cin FastIO::cin
#define cout FastIO::cout

const int N=1e5+5,B=140,K=N/B+5,T=B+5;
class Node
{
public:
	int pos,ip,k;
};
class node
{
public:
	int lt,rt,k;
}e[N];
int n,q,slen[N],bl[N],res,lst[N],nxt[N];
vector<Node>p[N];
string s[N],re[N];
int f[N][T<<1],g[N][T<<1];

class failtree //分块
{
public:

const int B=317;
vector<int>nbr[N];
int dfn[N],ed[N],tot,R[N],sumB[N],sum[N],knum,pos[N];

inline void add(int x,int y)
{
	nbr[x].push_back(y);
	return ;
}

void dfs(int cur)
{
	dfn[cur]=++tot;
	for(int nxt:nbr[cur])
		dfs(nxt);
	ed[cur]=tot;
	return ;
}

inline void init()
{
	for(int i=0;i<=tot;i+=B)
	{
		R[++knum]=min(tot,i+B-1);
		sumB[knum]=0;
		for(int j=i;j<=R[knum];++j)
			pos[j]=knum,sum[j]=0;
	}
	return ;
}

inline void update(int x)
{
	x=dfn[x];
	for(int i=pos[x];i<=knum;++i)
		sumB[i]++;
	for(int i=x;i<=R[pos[x]];++i)
		sum[i]++;
	return ;
}

inline int query(int x)
{
	int lt=dfn[x],rt=ed[x],y=pos[rt];
	x=pos[lt];
	int val=(lt==(R[x-1]+1)?0:sum[lt-1]);
	if(x==y)
		return sum[rt]-val;
	return (sum[R[x]]-val)+(sumB[y-1]-sumB[x])+sum[rt];
}

};

class ACAM //一棵正着一棵反着
{
public:

int Trie[N][26],fail[N],tot;
failtree flt;

inline void insert(string s)
{
	int u=0;
	for(char ch:s)
	{
		int t=ch-'a';
		if(Trie[u][t]==0)
			Trie[u][t]=++tot;
		u=Trie[u][t];
	}
	return ;
}

inline void build()
{
	queue<int>q;
	q.push(0);
	while(q.empty()==false)
	{
		int cur=q.front();
		q.pop();
		for(int i=0;i<26;++i)
		{
			if(Trie[cur][i]==0)
				Trie[cur][i]=Trie[fail[cur]][i];
			else
			{
				int nxt=Trie[cur][i];
				if(cur!=0)
					fail[nxt]=Trie[fail[cur]][i];
				flt.add(fail[nxt],nxt);
				q.push(nxt);
			}
		}
	}
	flt.dfs(0);
	flt.init();
	return ;
}

inline void update(string s)
{
	int u=0;
	for(char ch:s)
	{
		u=Trie[u][ch-'a'];
		flt.update(u);
	}
	return ;
}

}zdz,zdf;
int wzz[N],wzf[N],to[N];
int num[K][K][K],cn;
ll anst[N];
vector<int>sumz[K][K],sumf[K][K];
int len[N];

signed main()
{
	cin>>n>>q;
	for(int i=1;i<=n;i++)
	{
		cin>>s[i];
		len[i]=s[i].size();
		zdz.insert(s[i]);
		re[i]=s[i];
		reverse(re[i].begin(),re[i].end());
		zdf.insert(re[i]);
		if(len[i]>B)
			bl[++res]=i,slen[res]=slen[res-1]+len[i];
		if(len[i]>B+B)
		{
			wzz[i]=0;
			for(int j=0;j<len[i]-B-1;++j)
				wzz[i]=zdz.Trie[wzz[i]][s[i][j]-'a'];
			wzf[i]=0;
			for(int j=0;j<len[i]-B-1;++j)
				wzf[i]=zdf.Trie[wzf[i]][re[i][j]-'a'];
		}
	}
	zdz.build();
	zdf.build();
	int ttt=0;
	for(int i=1;i<=n;++i)
	{
		if(len[i]>B)
			ttt++;
		lst[i]=ttt;
	}
	ttt=res+1;
	for(int i=n;i>=1;--i)
	{
		if(len[i]>B)
			ttt--;
		nxt[i]=ttt;
	}
	for(int i=0;i<=res;++i)
		for(int j=1;j<=res;++j)
		{
			sumz[i][j].resize(len[bl[j]],0);
			sumf[i][j].resize(len[bl[j]],0);
		}
	for(int i=1;i<=res;++i)
	{
		zdz.update(s[bl[i]]);
		zdf.update(re[bl[i]]);
		for(int j=1;j<=res;++j)
		{
			int a=0,b=0;
			for(int k=0;k+1<len[bl[j]]-B;++k)
			{
				a=zdz.Trie[a][s[bl[j]][k]-'a'];
				b=zdf.Trie[b][re[bl[j]][k]-'a'];
				sumz[i][j][k]=zdz.flt.query(a);
				sumf[i][j][k]=zdf.flt.query(b);
			}
		}
	}
	for(int i=1;i<=q;++i)
	{
		int lt,rt,pos;
		cin>>lt>>rt>>pos;
		p[lt-1].push_back({pos,i,-1});
		p[rt].push_back({pos,i,1});
		e[i]=(node){lt,rt,pos};
	}
	zdz.flt.init();
	zdf.flt.init();
	for(int i=1;i<=n;i++)
	{
		zdz.update(s[i]);
		zdf.update(re[i]);
		for(Node cur:p[i])
		{
			int k=cur.pos,a=0,b=0;
			if(len[k]<=B+B)
			{
				for(int j=0;j+1<len[k];++j)
				{
					a=zdz.Trie[a][s[k][j]-'a'];
					b=zdf.Trie[b][re[k][j]-'a'];
					f[cur.ip][j]+=zdz.flt.query(a)*cur.k;
					g[cur.ip][j]+=zdf.flt.query(b)*cur.k;
				}
			}
			else
			{
				for(int j=0;j<=B-1;++j)
				{
					a=zdz.Trie[a][s[k][j]-'a'];
					b=zdf.Trie[b][re[k][j]-'a'];
					f[cur.ip][j]+=zdz.flt.query(a)*cur.k;
					g[cur.ip][j]+=zdf.flt.query(b)*cur.k;
				}
				a=wzz[k],b=wzf[k];
				int vw=B+B;
				for(int j=len[k]-B-1;j+1<len[k];++j)
				{
					a=zdz.Trie[a][s[k][j]-'a'];
					b=zdf.Trie[b][re[k][j]-'a'];
					f[cur.ip][--vw]+=zdz.flt.query(a)*cur.k;
					g[cur.ip][vw]+=zdf.flt.query(b)*cur.k;
				}
			}
		}
	}
	for(int i=1;i<=q;++i)
	{
		int k=e[i].k;
		ll sum=0;
		if(len[k]<=B+B)
		{
			for(int j=0;j+1<len[k];++j)
				sum+=1ll*f[i][j]*g[i][len[k]-2-j];
		}
		else
		{
			for(int j=0;j+1<=B;++j)
				sum+=1ll*f[i][j]*g[i][j+B]+1ll*f[i][j+B]*g[i][j];
		}
		if(len[k]>B)
		{
			int L=nxt[e[i].lt],rr=lst[e[i].rt];
			if(L<=rr)
			{
				if(num[L][rr][lst[k]]==0)
				{
					num[L][rr][lst[k]]=++cn;
					for(int j=B;j+1<len[k]-B;++j)
						anst[cn]+=1ll*(sumz[rr][lst[k]][j]-sumz[L-1][lst[k]][j])*
								      (sumf[rr][lst[k]][len[k]-2-j]-sumf[L-1][lst[k]][len[k]-2-j]);
				}
				sum+=anst[num[L][rr][lst[k]]];
			}
		}
		cout<<sum<<"\n";
	}
	return 0;
}
posted @ 2025-11-20 19:23  tmp_get_zip_diff  阅读(11)  评论(0)    收藏  举报