[Str记录]P5115 Check,Check,Check one two!
command_block
2021-06-26 15:46:57
**题意** : 给定长为 $n$ 的字符串 $S$。
记 ${\rm lcp}(i,j)$ 为 $S[i:]$ 与 $S[j:]$ 的最长公共前缀长度。
记 ${\rm lcs}(i,j)$ 为 $S[:i]$ 与 $S[:j]$ 的最长公共后缀长度。
给出常数 $k_1,k_2$ ,求下列式子的值。
$$\sum_{1\leq i<j \leq n}{\rm lcp}(i,j){\rm lcs}(i,j)[{\rm lcp}(i,j)\leq k_1][{\rm lcs}(i,j) \leq k_2]$$
答案对 $2^{64}$ 取模。
$n\leq10^5$ ,字符集为小写英文字母,时限$\texttt{2s}$。
------------
好题。
对于 $(i,j)$ ,显然 $S[i-{\rm lcs}(i,j)+1,i+{\rm lcp}(i,j)-1]$ 和 $S[j-{\rm lcs}(i,j)+1,j+{\rm lcp}(i,j)-1]$ 是完全相同的,且向两侧延伸的长度是极长的。
枚举所有这样的 $(i,j)$ 对应的极长串对,计算贡献。
对于一个子串 $T$ 的两次出现,如果这两次出现都是**极长**的(即向两侧扩展后不相同),则可以贡献。
具体地,贡献值为 :
$$\sum\limits_{k=1}^{|T|}[k\leq k_2]\big[|T|-k+1\leq k_1\big]k\big(|T|-k+1\big)$$
首先枚举中心(即 $(i,j)$)在串中的位置,然后将两侧可以延伸的距离乘起来求和。
上式中唯一的变量为 $|T|$ ,得知 $k_1,k_2$ 后可以 $O(n)$ 预处理。(具体方法见代码)
对 $S$ 建立 $\rm SAM$ 。
对于每个 $\rm SAM$ 节点,显然只有最长串可能产生极长串对。(其他串产生的串对都能扩展为最长串)
观察 $\text{parent-Tree}$ 上极长串对的结构。两个 $\rm edp$ 在它们的 $\rm lca$ 处才有可能产生极长串对,在更浅的祖先处则不再是极长的。
这提示我们在树上合并。
观察两个在 $\rm lca$ 处相遇的 $\rm edp$ ,它们的前面已经确定不能扩展,但后面仍然有可能扩展。
于是暴力记下后方下一个字符的情况。记 $f_u[c]$ 表示 $u$ 子树中的 $\rm edp$ 下一个是 $c$ 的个数。
当两组 $\rm edp$ 合并时,利用 $f$ 容易算出产生的极长串对。
复杂度 $O(n\Sigma)$。 若字符集较大,使用启发式合并可以做到 $O(n\log n)$。
```cpp
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>
#define pb push_back
#define ull unsigned long long
#define MaxN 100500
using namespace std;
struct Node{int t[26],len,f,ed;}a[MaxN<<1];
int tn,las;
void add(int c)
{
int np=++tn,p=las;las=np;
a[np].len=a[p].len+1;
for (;p&&!a[p].t[c];p=a[p].f)a[p].t[c]=np;
if (!p)a[np].f=1;
else {
int v=a[p].t[c];
if (a[v].len==a[p].len+1)a[np].f=v;
else {
int nv=++tn;a[nv]=a[v];
a[nv].len=a[p].len+1;
for (;p&&a[p].t[c]==v;p=a[p].f)a[p].t[c]=nv;
a[np].f=a[v].f=nv;
}
}
}
char s[MaxN];
int n,k1,k2;ull f[MaxN];
ull calc(int l,int r){return l<=r ? 1ull*(l+r)*(r-l+1)/2 : 0;}
void Init()
{
f[1]=1;
for (int t=1;t<n;t++){
int tl=max(1,t+2-k1),tr=min(t+1,k2);
f[t+1]=f[t]+calc(tl,tr);
int k=t-k1+1;
if (1<=k&&k<=tr)f[t+1]-=1llu*k1*(t-k1+1);
}
}
vector<int> g[MaxN<<1];
ull ans,o[MaxN<<1][27];
void dfs(int u)
{
if (a[u].ed){
if (a[u].ed!=n)o[u][s[a[u].ed+1]-'a']++;
o[u][26]++;
}
for (int i=0,v;i<g[u].size();i++){
dfs(v=g[u][i]);
ull buf=o[u][26]*o[v][26];
for (int i=0;i<26;i++)
buf-=o[u][i]*o[v][i];
ans+=buf*f[a[u].len];
for (int i=0;i<=26;i++)o[u][i]+=o[v][i];
}
}
int main()
{
scanf("%s%d%d",s+1,&k1,&k2);
n=strlen(s+1);tn=las=1;
Init();
for (int i=1;i<=n;i++)add(s[i]-'a');
for (int i=1,p=1;i<=n;i++)
a[p=a[p].t[s[i]-'a']].ed=i;
for (int i=2;i<=tn;i++)g[a[i].f].pb(i);
dfs(1);
printf("%lld\n",ans);
return 0;
}
```