因为涉及到对模板的理解,所以就着代码看会好一些。

让那些坚决不颓代码的人受委屈了。

我是对着wzz的板子默写的,可能不完全一样啊。

还有代码注释里都是我个人的理解,不保证正确,但欢迎指正。

可以有选择的浏览,单调栈的那个非模板部分可以不看,自己想。

因为写的稍微有点详细所以在网页上极丑,粘到自己gedit上看吧。

 #include<cstdio>
#include<algorithm>
#define int long long
char s[];
int x[],y[],sa[],rk[],l=,c[],m=,h[];
int sta[],sta2[],top,ans,tr[],tl[];
main(){
scanf("%s",s);
while(s[l]) l++;//统计长度。在末尾加上了一个空字符
for(int i=;i<=l;++i) c[s[i]?x[i]=s[i]-'a'+:]++;//统计各字符出现次数,顺便把字符串s转化为数串x
for(int i=;i<=m;++i) c[i]+=c[i-];//累加得到出现次数的前缀和,相当与划分出了多个区间
for(int i=l;~i;--i) sa[--c[x[i]]]=i;//初步确定每个范围里有哪些串,但内部排名未知
for(int len=,p=;len<=l+;len<<=,p=){//枚举前后两截(二元组)的长度,倍增求解每个串在长度为len<<1的所有串中的排名
for(int i=l;i>=l-len+;--i) y[p++]=i;//没有后半截的直接记录,y数组存的是前半截(也就是整个串)的起始位置,y有序因为空字符的字典序最小
for(int i=;i<=l;++i) if(sa[i]>=len) y[p++]=sa[i]-len;//有后半截的,那就记录下它对应的左半截,注意y已经是后半截有序的
for(int i=;i<=m;++i) c[i]=;//清空字符出现次数
for(int i=;i<=l;++i) c[x[i]]++;//重新累加每个长度为len的字符串的出现次数
for(int i=;i<=m;++i) c[i]+=c[i-];//还是前缀和,完全和循环外面的一样,划分出大致区间
for(int i=l;~i;--i) sa[--c[x[y[i]]]]=y[i];//对于每个串的前半截讨论其排名,前半截有序了,排名相同的按后半截也拍好序了(因为y已经有序)
p=;std::swap(x,y); x[sa[]]=;//清空p,xy数组滚动交换,确定排名第0的串是一个空串
for(int i=;i<=l;++i) x[sa[i]]=y[sa[i]]==y[sa[i-]]&&y[sa[i]+len]==y[sa[i-]+len]?p:++p;//类似离散化,求出已经区分开的串的种数,并更新字符串和字典序排名
if(p==l)break;else m=p;//如果已经全部区分开了,可以跳出循环,否则更新串种数然后继续区分
}
for(int i=;i<=l;++i) rk[sa[i]]=i;//求出sa的逆数组,表示排名为i的串的开始位置
for(int i=,k=;i<=l;h[rk[i++]]=k,k=k?k-:) while(s[i+k]==s[sa[rk[i]-]+k]) k++;//求height,s[i+k]可以写作s[sa[rk[i]]+k]更好理解,注意h[1]是无用的因为sa[1]是空串
//性质:排名为i,j(i<j)的后缀的lcp为min{height[k]|i+1≤k≤j}
//转化:已知一段序列,求所有字串的最小值的和--两次单调栈(内部元素是底小顶大),求解左右多大的区间能以这个值为最小值
sta[]=-;h[l+]=-;h[]=-;//与后面的弹栈元素等有关,-3直接入栈压底防止弹栈弹多了,其余两个都是用来强制所有正常元素弹栈
for(int i=;i<=l+;++i){//注意这里到了l+1,也就是用-2强制弹干净栈中所有的元素(除了压底的-3)
while(sta[top]>=h[i])tr[sta2[top--]]=i-;//当一个元素被弹出,一定是遇到了右侧第一个小于等于它的元素,那么自然以此为右端点的区间内最小值为这个元素
sta[++top]=h[i];sta2[top]=i;//入栈,记录位置
}
for(int i=l;i;--i){//倒着单调栈,求出它能控制的左端点,用h[1]=-1强制弹栈
while(sta[top]>h[i])tl[sta2[top--]]=i+;//注意这里没有等号,否则当有相等的height值时会重复计算(对于测试点abbab好像就不行)
sta[++top]=h[i];sta2[top]=i;
}
for(int i=;i<=l;++i)ans+=h[i]*(tr[i]-i+)*(i-tl[i]+);//区间右端点在i~tr[i]内任选,左端点在tl[i]~i内任选,这时区间最小值都是h[i],记录方案数乘权值
printf("%lld\n",((l-)*l*(l+)>>)-(ans<<));
}

最新文章

  1. Lodash.js的库
  2. php创建文件并写入信息
  3. java基础题目总结
  4. Microsoft SQL Server Compact 4.0&amp;&amp;ADO.NET Entity Framework 4.1&amp;&amp;MVC3
  5. Chrome编辑了样式或者JS之后自动同步回本地工程
  6. Unity3D实现赛车的灯光效果
  7. UML的9种图例解析
  8. 【python】正则表达式
  9. 杂乱无章之Oracle(二)
  10. Container With Most Water 解答
  11. bootstrap 简易模版
  12. JDK,JRE,JVM区别与联系(转)
  13. java的两种异常runtimeException和checkedException
  14. 破解Oracle ERP 密码
  15. C语言面试题大汇总之华为面试题 Eddy整理
  16. js数据结构与算法——二叉树
  17. CSS属性级Hack
  18. code自动补全
  19. 用一个应用场景理解ASP.NET Core Identity是什么?
  20. JAVAssist字节码操作

热门文章

  1. Scala 异常处理
  2. Flume 学习笔记之 Flume NG高可用集群搭建
  3. 安装vant2.2.7版本报错These dependencies were not found: vant/es/goods-action-big-btn in ./src/config/vant.config.js......
  4. C#中winform中panel重叠无法显示问题
  5. MOV与LEA
  6. PhpSpreadsheet 导出特定格式 — 广告请款单
  7. App自动化环境搭建
  8. splinter操作ie浏览器
  9. [NOIp2013] luogu P1966 火柴排队
  10. Python开发【第八篇】元组