洛谷2408不同字串个数/SPOJ 694/705 (后缀数组SA)
真是一个三倍经验好题啊。
我们来观察这个题目,首先如果直接整体计算,怕是不太好计算。
首先,我们可以将每个子串都看成一个后缀的的前缀。那我们就可以考虑一个一个后缀来计算了。
为了方便起见,我们选择按照字典序来一次插入每个后缀,然后每次考虑当前后缀会产生的新串和与之前插入的串重复的串(这里之所以可以这么考虑,是因为如果他会对后面的串产生重复的话,那么会在后面那个串加入的时候计算的)
那么我们考虑,一个排名为\(i\)的后缀,插入之后不考虑重复的话,会新增多少个子串呢?
不难发现是\(n-sa[i]+1\)个(注意后缀的位置编号是从前开始,而后缀的贡献是后面的子串个数。
那么重复的该怎么计算呢?
我们发现重复的部分实际是当前这个后缀和之前的后缀的\(lcp\)部分会重复,而且应该是最大的\(lcp\) (如果取小的会算少,直接求sum会算多)。
而有一个比较经典的性质就是,在字典序\(1到i\)中与\(i\)的\(lcp\)长度最长的,一定是\(i-1\),这里有两种理解方式,一个是越远差距越大,另一种是越靠前,取\(min\)的范围越大,\(min\)就会可能越小
那么枚举+计算,记得开\(long \ long\)就三倍经验辣
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define mk makr_pair
#define ll long long
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)) {if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)) {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
const int maxn = 2e5+1e2;
int rk[maxn],sa[maxn];
int wb[maxn];
int tmp[maxn];
char a[maxn];
int n;
int h[maxn],height[maxn];
void getsa()
{
int *x=rk,*y=tmp;
int s=128;
int p=0;
for (int i=1;i<=n;i++) x[i]=a[i],y[i]=i;
for (int i=1;i<=s;i++) wb[i]=0;
for (int i=1;i<=n;i++) wb[x[y[i]]]++;
for (int i=1;i<=s;i++) wb[i]+=wb[i-1];
for (int i=n;i>=1;i--) sa[wb[x[y[i]]]--]=y[i];
for (int j=1;p<n;j<<=1)
{
p=0;
for (int i=n-j+1;i<=n;i++) y[++p]=i;
for (int i=1;i<=n;i++) if (sa[i]>j) y[++p]=sa[i]-j;
for (int i=1;i<=s;i++) wb[i]=0;
for (int i=1;i<=n;i++) wb[x[y[i]]]++;
for (int i=1;i<=s;i++) wb[i]+=wb[i-1];
for (int i=n;i>=1;i--) sa[wb[x[y[i]]]--]=y[i];
swap(x,y);
p=1;
x[sa[1]]=1;
for (int i=2;i<=n;i++)
x[sa[i]]=(y[sa[i]]==y[sa[i-1]] && y[sa[i]+j]==y[sa[i-1]+j]) ? p : ++p;
s=p;
}
for (int i=1;i<=n;i++) rk[sa[i]]=i;
h[0]=0;
for (int i=1;i<=n;i++)
{
h[i]=max(h[i-1]-1,0);
while(i+h[i]<=n && sa[rk[i]-1]+h[i]<=n && a[i+h[i]]==a[sa[rk[i]-1]+h[i]]) h[i]++;
}
for (int i=1;i<=n;i++) height[i]=h[sa[i]];
}
int t;
void init()
{
memset(wb,0,sizeof(wb));
memset(rk,0,sizeof(rk));
memset(sa,0,sizeof(sa));
memset(tmp,0,sizeof(tmp));
memset(h,0,sizeof(h));
memset(height,0,sizeof(height));
}
int main()
{
//cin>>t;
//while (t--)
//{
n=read();
init();
scanf("%s",a+1);
getsa();
long long ans=0;
for (int i=1;i<=n;i++)
{
ans=ans+(long long)(n-sa[i]+1)-(long long)h[i];//这里可以理解成我们顺着字典序的顺序,加入每个后缀,将子串看成后缀的前缀
// 而每次加入会产生新的n-sa[i]+1个字串,其中重复的就是和之前的子串的某些lcp,而字典序上,在这个串前面,与某个串lcp最长的应该是i-1那个串(这里可以理解成越往前差距越大)
}
cout<<ans<<"\n";
// }
return 0;
}
洛谷2408不同字串个数/SPOJ 694/705 (后缀数组SA)的更多相关文章
- 洛谷P2408 不同字串个数 [后缀数组]
题目传送门 不同字串个数 题目背景 因为NOI被虐傻了,蒟蒻的YJQ准备来学习一下字符串,于是它碰到了这样一道题: 题目描述 给你一个长为N的字符串,求不同的子串的个数 我们定义两个子串不同,当且仅当 ...
- SPOJ 694/705 后缀数组
思路: 论文题*n Σn-i-ht[i]+1 就是结果 O(n)搞定~ //By SiriusRen #include <cstdio> #include <cstring> ...
- 【题解】洛谷P1032 [NOIP2002TG]字串变换(BFS+字符串)
洛谷P1032:https://www.luogu.org/problemnew/show/P1032 思路 初看题目觉得挺简单的一道题 但是仔细想了一下发现实现代码挺麻烦的 而且2002年的毒瘤输入 ...
- 【洛谷】P1032 字串变换
题目地址:https://www.luogu.org/problemnew/show/P1032 洛谷训练场BFS的训练题呀. “BFS不就是用队列的思想去遍历一切情况嘛.我已经不是小孩子了,我肯定能 ...
- 洛谷 P1032 【字串变换】
感觉这个题用一些常用的stl和string函数会非常简单..(难道就是考这两个的吗? vector<pair<string,string>>pos//用于变化 map<s ...
- 洛谷P2336 [SCOI2012]喵星球上的点名(后缀数组+莫队)
我学AC自动机的时候就看到了这题,想用AC自动机结果被学长码风劝退-- 学后缀数组时又看到了这题--那就写写后缀数组做法吧 结果码风貌似比当年劝退我的学长还毒瘤啊 对所有的模式串+询问串,不同串之间用 ...
- 【洛谷4770/UOJ395】[NOI2018]你的名字(后缀数组_线段树合并)
题目: 洛谷4770 UOJ395 分析: 一个很好的SAM应用题-- 一句话题意:给定一个字符串\(S\).每次询问给定字符串\(T\)和两个整数\(l\).\(r\),求\(T\)有多少个本质不同 ...
- 洛谷P4493 [HAOI2018]字串覆盖(后缀自动机+线段树+倍增)
题面 传送门 题解 字符串就硬是要和数据结构结合在一起么--\(loj\)上\(rk1\)好像码了\(10k\)的样子-- 我们设\(L=r-l+1\) 首先可以发现对于\(T\)串一定是从左到右,能 ...
- 【洛谷P3411】字串变换
题解:普通的 BFS 没什么可说的,字符串处理是这道题的难点,同时需要注意哈希判重. 另外,对于 \(string\) 类来说,学到了一个 push_back((char)) 操作. c++strin ...
随机推荐
- for循环操作(for...in、forEach)
1.for...in语句用于对数组或者对象的属性进行循环操作,是for循环的一种. 注意:该方法可用于数组或对象. 语法: for(变量 in 对象/数组){} 如: var obj = { nam ...
- JSTL标签报错-http://java.sun.com/jsp/jstl/core
考虑为tomcat缺少相关的包 导入就好了 导入jstl-api-1.2.jar 以及standard-1.1.2.jar 然后重启服务 更多java学习,请进本人小博客-https://zhangj ...
- Aggressor Script 开发-Powershell 免杀
转载https://www.jianshu.com/p/f158a9d6bdcf 前言 在接触到Cobalt Strike的时候就知道有各种插件,想象着那天也可以自己学习编写一个.在之前分析Cobal ...
- 如何获取 Android CPU 核心数 (Java/C++)
1 前言 最近学习Power HAL方面相关知识,透过Power HAL 去配置CPU的Freq需要先确定 CPU 核数.便先了解如何获取 Android CPU 核数. 2 Java层获取方式 // ...
- noip模拟47
原版在 \(linux\) 本地写完没保存关机给没了-- 再简单写一下 \(t2\) 用 \(dp\) 转移 \(f[i]=\sum _ {j=last[a[i]]}^{i-1} f[j]\) 用前缀 ...
- Python - 面向对象编程 - 实战(6)
需求 设计一个培训机构管理系统,有总部.分校,有学员.老师.员工,实现具体如下需求: 有多个课程,课程要有定价 有多个班级,班级跟课程有关联 有多个学生,学生报名班级,交这个班级对应的课程的费用 有多 ...
- 乐肴2.0环境改名后,需要删除原来的软链接(public/storage)
rm -rf public/storage php artisan storage:link
- 面试HashMap你都扛不住,还想拿到offer?
当我们面试Java开发岗位时,面试官问的频率出现最多的问题,就是这个HashMap,不管是传统型公司还是互联公司,HashMap是必问的,所以作者爆肝整理了HashMap的23个问题以及答案,请查收! ...
- NLP与深度学习(四)Transformer模型
1. Transformer模型 在Attention机制被提出后的第3年,2017年又有一篇影响力巨大的论文由Google提出,它就是著名的Attention Is All You Need[1]. ...
- C# 获得当前方法 和 方法调用链 的 方法
一个获得方法名的方法,depth表示调用此方法的回溯深度. 比如,A方法调用B方法,B方法调用GetCurrentMethodFullName(2),那么得到的结果是A方法的全名(namespace+ ...