题解:P7409 SvT
看各位大佬都是拿 SA 或 SAM 加虚树的方法写的,但是本蒟蒻既不会 SA 也不会虚树……
所以我就用 SAM 加启发式合并过了这道题。
思路
首先,将字符串翻转,这样每次询问就变成了求若干个前缀两两的 LCS(最长公共后缀)的长度之和。这是一个经典的 SAM 问题:对于每两个前缀对应的 parent 树上节点,它们在 parent 树上的 LCA 对应的就是它们的 LCS,因此建出 SAM,在 parent 树上 dp 即可。
那如何处理多组询问呢?最暴力的想法就是对树上每一个节点开一个桶,其下标为询问编号,来纪录每一组询问在某个子树内有多少节点。考虑优化,使用 map 来实现桶,转移时启发式合并,就可以实现均摊 \(log\) 级别的转移。
实际实现时,用 map 或 unordered_map 都会 T 四个点,必须使用常数更小的 gp_hash_table。
代码
#include <bits/extc++.h>
using namespace std;
using namespace __gnu_pbds;
#define ll long long
#define pii pair<int,int>
const int N=5e5+5,M=3e6+5;
const ll P=23333333333333333;
int rd()
{
int fl=1,x=0;char c=getchar();
while(c>'9' || c<'0')
{
if(c=='-')
fl=-1;
c=getchar();
}
while(c<='9' && c>='0')
{
x=x*10+c-'0';
c=getchar();
}
return x*fl;
}
void wt(ll x,char c='~')
{
if(x<0)
{
putchar('-');
x=-x;
}
if(x>9)
wt(x/10);
putchar(x%10+'0');
if(c!='~')
putchar(c);
}
int n,m,pos[N];
ll ans[M];
char s[N];
int lst=1,tot=1,ch[N*2][26],fa[N*2],len[N*2];
gp_hash_table<int,int> bk[N*2];
vector<int> a[N*2];
void insert(int c,int id)
{
int p=lst,np=lst=++tot;
len[np]=len[p]+1;
pos[id]=np;
for(;p&&!ch[p][c];p=fa[p])
ch[p][c]=np;
if(!p)
fa[np]=1;
else
{
int q=ch[p][c];
if(len[q]==len[p]+1)
fa[np]=q;
else
{
int nq=++tot;
fa[nq]=fa[q];
memcpy(ch[nq],ch[q],sizeof(ch[q]));
len[nq]=len[p]+1;
fa[np]=fa[q]=nq;
for(;p&&ch[p][c]==q;p=fa[p])
ch[p][c]=nq;
}
}
}
void dfs(int x)
{
for(int y:a[x])
{
dfs(y);
if(bk[y].size()>bk[x].size())//启发式合并
swap(bk[x],bk[y]);
for(pii p:bk[y])
{
if(bk[x].find(p.first)!=bk[x].end())
(ans[p.first]+=1ll*len[x]*p.second*bk[x][p.first]%P)%=P;
bk[x][p.first]+=p.second;
}
bk[y].clear();//必须清空,否则会 MLE
}
}
int main()
{
scanf("%d%d%s",&n,&m,s+1);
reverse(s+1,s+1+n);
for(int i=1;i<=n;i++)
insert(s[i]-'a',i);
for(int i=1;i<=m;i++)
{
int t=rd();
while(t--)
{
int p=rd();
p=n-p+1;
bk[pos[p]][i]=1;
}
}
for(int i=2;i<=tot;i++)
a[fa[i]].push_back(i);
dfs(1);
for(int i=1;i<=m;i++)
wt(ans[i],'\n');
return 0;
}

浙公网安备 33010602011771号