UOJ523 【美团杯2020】半前缀计数 题解
题目描述
给定长为 \(n\) 的字符串 \(s\) ,求可以被表示成 \(s[1\sim i]+s[j\sim k]\) 的本质不同字符串个数,其中 \(0\le i\lt j\le k\le n\) 。
数据范围
- \(1\le n\le 10^6\) 。
时间限制 \(\texttt{2s}\) ,空间限制 \(\texttt{512MB}\) 。
分析
假设字符串 \(t\) 符合要求,枚举 \(lcp(s,t)=i\) ,那么要求剩余部分是 \(s[i+1\sim n]\) 的子串并且首字母不是 \(s_{i+1}\) 。
对反串建 \(\text{SAM}\) ,倒序扫描 \(i\) 。
本质不同子串个数可以增量式维护,具体方法参见P4070 [SDOI2016] 生成魔咒。
再容斥掉首字母为 \(s_{i+1}\) 的情况,由于我们是对反串建 \(\text{SAM}\) ,所以需要统计最后一个字符为 \(s_{i+1}\) 的本质不同子串个数。
注意到同一个 \(endpos\) 集合中的串最后一个字符相同,对每个字符 \(c\) 维护以其结尾的本质不同子串个数,插入时动态更新即可。
最后别忘了空串,时间复杂度 \(\mathcal O(n)\) 。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e6+5;
int n,lst=1,tot=1;
ll cur,res;
ll cnt[26];
char s[maxn];
struct node
{
int ch[26];
int fa,len;
}f[maxn];
void insert(int c)
{
int p=lst,np=lst=++tot;
f[np].len=f[p].len+1;
while(p&&!f[p].ch[c]) f[p].ch[c]=np,p=f[p].fa;
if(!p) f[np].fa=1;
else
{
int q=f[p].ch[c];
if(f[q].len==f[p].len+1) f[np].fa=q;
else
{
int nq=++tot;
f[nq]=f[q],f[nq].len=f[p].len+1;
f[q].fa=f[np].fa=nq;
while(p&&f[p].ch[c]==q) f[p].ch[c]=nq,p=f[p].fa;
}
}
cur+=f[np].len-f[f[np].fa].len;
cnt[c]+=f[np].len-f[f[np].fa].len;
}
int main()
{
scanf("%s",s+1),n=strlen(s+1);
for(int i=n;i>=1;i--)
{
insert(s[i]-'a');
res+=cur-cnt[s[i]-'a']+1;
}
printf("%lld\n",++res);
return 0;
}
本文来自博客园,作者:peiwenjun,转载请注明原文链接:https://www.cnblogs.com/peiwenjun/p/16610884.html
浙公网安备 33010602011771号