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;
}

浙公网安备 33010602011771号