Codeforces
题解
]
CF1780G Delicious Dessert 题解
不会 SAM,也不会 SA,原来 SA 还有这种用法。
按 height 从大到小枚举,记当前长度为 $k$,那么把所有 $h_i=k$ 的 $i-1,i$ 所在的并查集合并。
可以证明,如果我们已经合并了 height $\ge k$ 的部分,那么每个连通块的大小就是某个长度为 $k$ 的子串的出现次数。
那么出现次数能被长度整除就只需数出大小为 $k$ 的倍数的联通块数量。
#include<bits/stdc++.h>
#define siz(x) int((x).size())
#define all(x) std::begin(x),std::end(x)
#define fi first
#define se second
using namespace std;
using unt=unsigned;
using loli=long long;
using lolu=unsigned long long;
using pii=pair<int,int>;
mt19937_64 rng(random_device{}());
constexpr int N=1e6+7;
int n,m,cnt[N],rk[N],sa[N],tmp[N],h[N],id[N];
string s;
vector<int>b[N];
int c[N],fa[N],sz[N];
int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
signed main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
ios::sync_with_stdio(false);cin.tie(nullptr);
cin>>n>>s;s=' '+s;
m=300;
memset(cnt,0,sizeof(int)*(m+1));
for(int i=1;i<=n;i++)cnt[rk[i]=s[i]]++;
for(int i=1;i<=m;i++)cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--)sa[cnt[rk[i]]--]=i;
for(int w=1;w<n;w<<=1){
int p=0;
for(int i=n-w+1;i<=n;i++)id[++p]=i;
for(int i=1;i<=n;i++)
if(sa[i]>w)id[++p]=sa[i]-w;
memset(cnt,0,sizeof(int)*(m+1));
for(int i=1;i<=n;i++)cnt[rk[i]]++;
for(int i=1;i<=m;i++)cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--)sa[cnt[rk[id[i]]]--]=id[i];
m=0;
for(int i=1;i<=n;i++)
if(rk[sa[i]]==rk[sa[i-1]]&&rk[sa[i]+w]==rk[sa[i-1]+w])tmp[sa[i]]=m;
else tmp[sa[i]]=++m;
memcpy(rk+1,tmp+1,sizeof(int)*n);
}
if(n!=1)for(int i=1,k=0;i<=n;i++){
if(k)k--;
while(s[sa[rk[i]]+k]==s[sa[rk[i]-1]+k])k++;
h[rk[i]]=k;
}
for(int i=2;i<=n;i++)
b[h[i]].push_back(i);
loli ans=0;
for(int i=1;i<=n;i++)
fa[i]=i,sz[i]=1;
c[1]=n;
for(int k=n;k>=1;k--){
for(int i:b[k]){
int x=find(i-1),y=find(i);
if(x==y)continue;
c[sz[x]]--;
c[sz[y]]--;
fa[y]=x;
sz[x]+=sz[y];
c[sz[x]]++;
}
for(int j=k;j<=n;j+=k)
ans+=1ll*j*c[j];
}
cout<<ans;
return 0;
}