【题解】「AHOI2013」 差异

题目描述
给定一个长度为 n n n 的字符串 S S S,令 T i T_i Ti 表示它从第 i i i 个字符开始的后缀。求 ∑ 1 < = i < j < = n n l e n ( T i ​ ) + l e n ( T j ​ ) − 2 × l c p ( T i ​ , T j ​ ) \sum_{1<=i<j<=n}^{n}len(Ti​)+len(Tj​)−2×lcp(Ti​,Tj​) 1<=i<j<=nnlen(Ti)+len(Tj)2×lcp(Ti,Tj)

solution:
主要是一个单调栈+后缀数组模板的运用。发现每个区间的值就是这个区间的最小值,而区间的最小值一般用单调栈来维护。

本人不是很会维护单调栈,所以当时找到了两种比较好的维护方法自己的wa了

法一.
由于是正序枚举,且是区间最小,所以若i<j,且height[i]>height[j],那么j后面的位置一定不会以height[i]作为高度,因为j更近,只要i能成为候选答案,j就会成为最优的答案。对于后面的点来说,只需满足j即可,所以i是冗余的,应该将i弹出。

所以这个单调栈一定是单调递增的。

我们考虑将右端点固定下来。设 f [ j ] f[j] f[j]表示以 j j j结尾的区间的贡献和。

那么我们怎么把 f [ j ] f[j] f[j]求出来呢?这个时候就要转移了:
f [ i ] = f [ p ] + ( i − p ) ∗ h [ i ] ( h [ p ] < h [ i ] ∣ p < i ) f[i]=f[p]+(i-p)*h[i](h[p]<h[i] | p<i) f[i]=f[p]+(ip)h[i]h[p]<h[i]p<i

注意是否严格递增都是对的。

#include<bits/stdc++.h> #define int long long using namespace std; //建一个trie树 //既然这样,那么lcp(a,b)等价于lca(a,b),为什么还要用height数组呢? //建树的时间复杂度最坏是O(n^2)吗? const int N=5e5+5; const int Maxchar=26; char s[N]; int n,m,num,x[N],y[N],c[N],sa[N]; int h[N],height[N],rk[N]; int q[N],w[N],tp; int res,res2,f[N]; void solve() { m=Maxchar; for(int i=1;i<=n;i++) c[x[i]=s[i]-'a'+1]++; for(int i=2;i<=m;i++) c[i]+=c[i-1]; for(int i=n;i>=1;i--) sa[c[x[i]]--]=i; for(int k=1;k<=n;k<<=1) { num=0; for(int i=n-k+1;i<=n;i++) y[++num]=i; for(int i=1;i<=n;i++) if(sa[i]>k) y[++num]=sa[i]-k; for(int i=1;i<=m;i++) c[i]=0; for(int i=1;i<=n;i++) c[x[i]]++; for(int i=2;i<=m;i++) c[i]+=c[i-1]; for(int i=n;i>=1;i--) sa[c[x[y[i]]]--]=y[i]; for(int i=1;i<=n;i++) y[i]=x[i],x[i]=0; x[sa[1]]=1;num=1; for(int i=2;i<=n;i++) x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num; if(m==n) break; m=num; } } void solve2() { int k=0; for (int i=1; i<=n; ++i) rk[sa[i]]=i; for (int i=1; i<=n; ++i) { if (rk[i]==1) continue;//第一名height为0 if (k) --k;//h[i]>=h[i-1]-1; int j=sa[rk[i]-1]; while (j+k<=n && i+k<=n && s[i+k]==s[j+k]) ++k; height[rk[i]]=k;//h[i]=height[rk[i]]; } } void solve3() { //单调栈维护递增序列(计算过程有点恶心) res=n*(n-1)*(n+1)/2; q[1]=-1,w[1]=1,tp=1;//(l,r],故不包括位置1 for(int i=2;i<=n;i++) { //1.计算lx和ex //2.递推计算f[x],其中f[x]表示以x为结尾的点对的贡献 while(tp>0&&height[i]<=q[tp]) { tp--; } f[i]=f[w[tp]]+(i-w[tp])*height[i]; q[++tp]=height[i],w[tp]=i; res2+=f[i]*2; } } signed main() { scanf("%s",s+1); n=strlen(s+1); solve(); solve2(); solve3(); printf("%lld",res-res2); }

法二.
这个思路要抽象一些。

首先还是维护单调递增的栈。这里必须是不严格递增。

令L[i]表示i往左边最多扩展的位置,R[i]表示i往右边最多扩展的位置,则点i的贡献为 ( i − L [ i ] ) ∗ ( R [ i ] − i ) ∗ h e i g h t [ i ] (i-L[i])*(R[i]-i)*height[i] (iL[i])(R[i]i)height[i]

为什么是正确的呢?假如i与相邻的数不同,那么以h[i]扩展出来的左右端点一定不会重复。如果左右相等,我们仔细观察下面代码,发现L[i]=i(除了这段连续的数中最左边的数有可能往左拓展以外),而右端点都是相同的。此时我们可以发现恰好把每种可能的左端点都枚举了一遍,且没有重复。

总结:首先,我们根据区间最小值进行分类,分别计算答案。其次,我们不允许它向和它相等且在它前面的数扩展,有效避免了重复。

于是就解决了。

#include <cstdio> #include <cstring> typedef long long LL; const int MN = 500005; int N; char str[MN]; int M; int rk[MN], rk2[MN], SA[MN], SA2[MN]; int buk[MN], cnt; int Height[MN]; void GetHeight() { int k = 0; for (int i = 1; i <= N; ++i) { if (rk[i] == 1) { k = Height[1] = 0; continue; } if (k) --k; int j = SA[rk[i] - 1]; while (i + k <= N && j + k <= N && str[i + k] == str[j + k]) ++k; Height[rk[i]] = k; } } void Rsort() { for (int i = 1; i <= M; ++i) buk[i] = 0; for (int i = 1; i <= N; ++i) ++buk[rk[i]]; for (int i = 1; i <= M; ++i) buk[i] += buk[i - 1]; for (int i = N; i >= 1; --i) SA[buk[rk[SA2[i]]]--] = SA2[i]; } void GetSA() { M = 26; for (int i = 1; i <= N; ++i) rk[i] = str[i] - 'a' + 1, SA2[i] = i; Rsort(); for (int j = 1; j < N; j <<= 1) { int P = 0; for (int i = N - j + 1; i <= N; ++i) SA2[++P] = i; for (int i = 1; i <= N; ++i) if (SA[i] > j) SA2[++P] = SA[i] - j; Rsort(); rk2[SA[1]] = P = 1; for (int i = 2; i <= N; ++i) { if (rk[SA[i]] != rk[SA[i - 1]] || rk[SA[i] + j] != rk[SA[i - 1] + j]) ++P; rk2[SA[i]] = P; } for (int i = 1; i <= N; ++i) rk[i] = rk2[i]; M = P; if (M == N) break; } GetHeight(); } int st[MN], t; int L[MN], R[MN]; int main() { scanf("%s", str + 1); N = strlen(str + 1); GetSA(); st[t = 1] = 1; for (int i = 2; i <= N; ++i) { while (t && Height[st[t]] > Height[i]) R[st[t--]] = i; L[i] = st[t]; st[++t] = i; } while (t) R[st[t--]] = N + 1; LL Ans = (LL)(N - 1) * N * (N + 1) / 2; for (int i = 2; i <= N; ++i) Ans -= 2ll * (R[i] - i) * (i - L[i]) * Height[i]; printf("%lld\n", Ans); return 0; }

__EOF__

本文作者仰望星空的蚂蚁
本文链接https://www.cnblogs.com/cqbzly/p/17530370.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   仰望星空的蚂蚁  阅读(6)  评论(0编辑  收藏  举报  
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」
点击右上角即可分享
微信分享提示