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)\)。
对于其他的部分一定满足 \(i>B\),此时这个前缀只可能由 \(|s_i| > B\) 的字符串 \(s_i\) 作贡献,而这样的字符串 \(s_i\) 只有 \(O(\frac{m}{B})\) 个,而且 \(i < |s_k|-B\),所以后缀也是只有 \(O(\frac{m}{B})\) 个的,有因为 \(|s_k| > 2B\),所以 \(k\) 的个数也只有 \(O(\frac{m}{B})\) 个。
若 \(|s_i| > B\),我们设 \(v_i = 1\),否则 \(v_i = 0\)。
因此假设我们处理第 \(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::coutconst 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;
}