2019HNCPC Distinct Substrings (扩展KMP)
2019HNCPC Distinct Substrings
Mean
给定一个\(n\)个整数的字符串,定义\(f(s_1,s_2,...,s_n)\)为本质不同的子串个数,
对于每个整数\(c∈[1,m]\),分别输出\(f(s_1,s_2,...,s_n)-f(s_1,s_2,...,s_n,c)\).
\(n,m<=1e6\)
Sol
扩展\(kmp\).
似乎是个经典做法?
参考OIWKI
给定一个长度为 \(n\)的字符串\(s\),计算\(s\)的本质不同子串的数目。
考虑计算增量,即在知道当前\(s\)的本质不同子串数的情况下,计算出在\(s\)末尾添加一个字符后的本质不同子串数。
令\(k\)为当前\(s\)的本质不同子串数。我们添加一个新的字符\(c\)至\(s\)的末尾。显然,会出现一些以\(c\)结尾的新的子串(以 \(c\)结尾且之前未出现过的子串)。
设串 \(t\)是\(s+c\)的反串(反串指将原字符串的字符倒序排列形成的字符串)。我们的任务是计算有多少\(t\)的前缀未在\(t\) 的其他地方出现。考虑计算 \(t\) 的 \(Z\) 函数并找到其最大值 \(zmax\)。则 \(t\) 的长度小于等于 \(zmax\) 的前缀的反串在 \(s\) 中是已经出现过的以\(c\)结尾的子串。
所以,将字符\(c\) 添加至 \(s\) 后新出现的子串数目为 \(|t|-zmax\) 。
算法时间复杂度为\(O(n^2)\)。
值得注意的是,我们可以用同样的方法在 \(O(n)\)时间内,重新计算在端点处添加一个字符或者删除一个字符(从尾或者头)后的本质不同子串数目。
按照上述做法,先将\((s_1,s_2,...,s_n)\)翻转成\((s_n,s_{n-1},...,s_1)\),然后求一下\(Z\)函数。
最后考虑若加入\(c\),在对于所有\((s_n,s_{n-1},...,s_1)\)中,\(s_i=c\),则需要将\(z_{i+1}\)代入\(c\)的情况取\(Max\),开一个桶维护一下即可。
剩下的就是如上的贡献计算了,具体看代码。
Code
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+10;
int mx[N];
typedef long long ll;
const ll mod = 1e9+7;
// C++ Version
int a[N];
int s[N];
void z_function(int s[],int n) {
for (int i = 1, l = 0, r = 0; i < n; ++i) {
if (i <= r && a[i - l] < r - i + 1) {
a[i] = a[i - l];
} else {
a[i] = max(0, r - i + 1);
while (i + a[i] < n && s[a[i]] == s[i + a[i]]) ++a[i];
}
if (i + a[i] - 1 > r) l = i, r = i + a[i] - 1;
}
}
int n,m;
int main(){
//freopen("in.txt","r",stdin);
while(~scanf("%d%d",&n,&m)){
int maxx=0;
for(int i=0;i<n;++i){
scanf("%d",&s[i]);
maxx=max(maxx,s[i]);
a[i]=0;
}
for(int i=0;i<=m;++i){
mx[i]=0;
}
reverse(s,s+n);
z_function(s,n);
for(int i=0;i<n-1;++i){
mx[s[i]]=max(mx[s[i]],1+a[i+1]);
}
mx[s[n-1]]=max(mx[s[n-1]],1);
ll ans=0;
ll jc=1;
for(int i=1;i<=m;++i){
jc = jc*3%mod;
if(mx[i]==0){
ans^= ((n+1)*jc%mod);
}
else{
ans^= (((n+1)-mx[i])*jc%mod);
}
}
printf("%lld\n",ans);
}
return 0;
}