常用STL方法
manacher算法
字符串Hash
KMP
4.1普通KMP
4.2扩展KMP
Trie(字典树)
5.1 字典树
5.1 01字典树
自动机
6.1 AC自动机
6.2 AC自动机上的动态规划
6.3 回文自动机(回文树)
后缀数组
7.1后缀数组的常见用法
后缀自动机(SAM)
1.对于字符串问题,最好使用char []来存储,不要用string,否则可能会占用大量内存及减低速度
2.strlen(char []),以及相似方法的复杂度均为O(n),千万不要用在循环里!
任意进制转换:
itoa(int n,char* s,int r) //将10进制n转换为r进制并赋给s
string:
流:
#include
stringstream stream; //创建名为stream的流
stream.clear //重复使用必须清空
string a,str;
stream(a); //将字符串a放入流
while(a>>str) //将a流入str中,以空格为拆分进行输出
cout<< str<< endl;
迭代器:
string::iterator it //创建名为it的迭代器
反转:
reverse(s.begin(), s.end()); //原地反转
s1.assign(s.rbegin(), s.rend()); //反转并赋给s1
大小写转换:
transform(s.begin(), s.end(), s.begin(), ::toupper);
transform(s.begin(), s.end(), s.begin(), ::tolower);
类型转换:
string ->int : string s("123");
int i = atoi(s.c_str());
int -> string: int a;
stringstream(s) >> a;
子串:
string substr(int pos = 0,int n = npos) const;//返回pos开始的n个字符组成的字符串
插入:s.insert(int p0,string s) //在p0位置插入字符串s
更改:
s.assign(str); //直接
s.assign(str,1,3);//如果str是”iamangel” 就是把”ama”赋给字符串
s.assign(str,2,string::npos);//把字符串str从索引值2开始到结尾赋给s
s.assign(“gaint”); //不说
s.assign(“nico”,5);//把’n’ ‘I’ ‘c’ ‘o’ ‘\0’赋给字符串
s.assign(5,’x’);//把五个x赋给字符串
删除:
s.erase(13);//从索引13开始往后全删除
s.erase(7,5);//从索引7开始往后删5个
iterator erase(iterator it);//删除it指向的字符,返回删除后迭代器的位置
iterator erase(iterator first, iterator last);//删除[first,last)之间的所有字符,返回删除后迭代器的位置
查找:
int find(char c, int pos = 0) const;//从pos开始查找字符c在当前字符串的位置
int find(const char *s, int pos = 0) const;//从pos开始查找字符串s在当前串中的位置
int find(const char *s, int pos, int n) const;//从pos开始查找字符串s中前n个字符在当前串中的位置
int find(const string &s, int pos = 0) const;//从pos开始查找字符串s在当前串中的位置。
删除所有特定字符:
str.erase(std::remove(str.begin(), str.end(), 'a'), str.end());
删除所有重复字符:
要求对象有序O(n+n),如果先排序O(nlogn+n+n)
str.erase(unique(str.begin(),str.end(),str.end());
能求出以任一点为中点的回文半径(回文串长度),O(n)
#include
#include
#include
#include
using namespace std;
const int maxl=1100005;
int p[2*maxl+5]; //p[i]-1表示以i为中点的回文串长度
int Manacher(string s)
{
string now;
int len=s.size();
for(int i=0;i<len;i++) //将原串处理成%a%b%c%形式,保证长度为奇数
{
now+='%';
now+=s[i];
}
now+='%';
int len=now.size();
int pos=0,R=0;
for (int i=0;i<len;i++)
{
if (i<R) p[i]=min(p[2*pos-i],R-i); else p[i]=1;
while (0<=i-p[i]&&i+p[i]<len&&now[i-p[i]]==now[i+p[i]]) p[i]++;
if (i+p[i]>R) {pos=i;R=i+p[i];}
}
int MAX=0;
for (int i=0;i<len;i++)
{
cout<<i<<" : "<<p[i]-1<<endl; //p[i]-1为now串中以i为中点的回文半径,即是s中最长回文串的长度
cout<<now.substr(i-p[i]+1,2*p[i]-1)<<endl;
MAX=max(MAX,p[i]-1);
}
return MAX; //最长回文子串长度
}
string a;
int main()
{
std::ios::sync_with_stdio(false);
while(cin>>a)
cout<<Manacher(a)<<endl;
return 0;
}
hash[i]=(hash[i-1]*seed+str[i])%MOD
将字符串通过hash函数映射成一个数字,并由此判断是否重复等,近似字符串匹配
#include
#include
#include
#include
using namespace std;
const int mod=1e6+13;
unordered_set<long long>hash_table;
int eid=0,p[mod];
struct node
{
long long next;
string s;
}e[100005];
void insert(int u,string str)
{
e[eid].s=str;
e[eid].next=p[u];
p[u]=eid++;
}
string str;
unsigned int BKDRHash(string str) //unsigned int 在溢出时会自动取模2^32??
{
unsigned int seed = 131; // 31 131 1313 13131 131313 etc..
unsigned int hash = 0;
for(int i=0;i<str.size();i++)
hash = (hash * seed + (str[i]))%mod;
return (hash & 0x7FFFFFFF)%mod;
}
// AP Hash Function
unsigned int APHash(string str)
{
unsigned int hash = 0;
int i;
for (i=0;i<str.size(); i++)
{
if ((i & 1) == 0)
hash ^= ((hash << 7) ^ (str[i]) ^ (hash >> 3));
else
hash ^= (~((hash << 11) ^ (str[i]) ^ (hash >> 5)));
}
return (hash & 0x7FFFFFFF);
}
int hash_in(string s)
{
long long u=BKDRHash(s);
for(int i=p[u];i!=-1;i=e[i].next) //使用链式前向星模拟链表进行冲突判断
{
string v=e[i].s;
if(v==s) return 0;
}
insert(u,s);
return 1;
}
int main()
{
memset(p,-1,sizeof(p));
while(cin>>str)
{
cout<<str<<endl;
cout<<BKDRHash(str)<<endl;
cout<<APHash(str)<<endl;
cout<<hash_in(str)<<endl;
}
return 0;
}
字符串匹配,并求出匹配位置,O(n+m)
#include
#include
using namespace std;
int Next[100005]; //next[i]表示,在t[0,i)中,所有匹配真前缀和真后缀的长度
int n,m;
string s,t;
void GetNext() //原始版本,原始next[i]表示,在t[0,i)中,所有匹配真前缀和真后缀的长度值右移一位,然后初值赋为-1而得
{
Next[0] = -1;
int k = -1;
int j = 0;
int len=t.size();
while (j < len - 1)
{
//p[k]表示前缀,p[j]表示后缀
if (k == -1 || t[j] == t[k])
{
++k;
++j;
Next[j] = k;
}
else
{
k = Next[k];
}
}
}
void getnext() //优化版,对于重复部分效果更好
{
int k=Next[0]=-1; //模式串指针,将next[0]=-1 表示一个哨兵,即是一个通配符,可以和任何字符匹配
int j=0; //主串指针
int len=t.size();
while(j<len-1)
{
if(k==-1 || t[j]==t[k]) //匹配
{
k++;
j++;
if(t[j]!=t[k])
Next[j]=k;
else
Next[j]=Next[k];
}
else //失配
k=Next[k];
}
}
int KMP() //串全部从0开始
{
getnext();
int i=0;
int j=0;
int lens=s.size();
int lent=t.size();
while(i<lens && j<lent)
{
if(j==-1 || s[i]==t[j])
{
i++;
j++;
}
else
j=Next[j];
}
if(j==lent)
return i-j;
else
return -1;
}
int main()
{
while(cin>>s>>t)
cout<<KMP()<<endl;
return 0;
}
求S的所有后缀与T的最长公共前缀,O(n+m)
#include
using namespace std;
const int maxn=10086; //字符串长度最大值
int Next[maxn],exnext[maxn]; //ex数组即为extend数组
string s,t;
//预处理计算Next数组
void getnext(string str) //next[i]表示,t[i...m]与t[1...m]中的最长公共前缀
{
int i=0,j,po,len=str.size();
Next[0]=len;//初始化Next[0]
while(str[i]==str[i+1]&&i+1<len)//计算Next[1]
i++;
Next[1]=i;
po=1;//初始化po的位置
for(i=2; i<len; i++)
{
if(Next[i-po]+i<Next[po]+po)//第一种情况,可以直接得到Next[i]的值
Next[i]=Next[i-po];
else//第二种情况,要继续匹配才能得到Next[i]的值
{
j=Next[po]+po-i;
if(j<0)j=0;//如果i>po+Next[po],则要从头开始匹配
while(i+j<len&&str[j]==str[j+i])//计算Next[i]
j++;
Next[i]=j;
po=i;//更新po的位置
}
}
}
//计算extend数组
void exkmp(string s,string t) //exnext[i],表示s[i...n]与t[1...m]中的最长公共前缀
{
int i=0,j,po,len=s.size(),l2=t.size();
getnext(t);//计算子串的Next数组
while(s[i]==t[i] && i<l2&&i<len)//计算ex[0]
i++;
exnext[0]=i;
po=0;//初始化po的位置
for(i=1; i<len; i++)
{
if(Next[i-po]+i<exnext[po]+po)//第一种情况,直接可以得到ex[i]的值
exnext[i]=Next[i-po];
else//第二种情况,要继续匹配才能得到ex[i]的值
{
j=exnext[po]+po-i;
if(j<0)j=0;//如果i>ex[po]+po则要从头开始匹配
while(i+j<len&&j<l2&& s[j+i]==t[j])//计算ex[i]
j++;
exnext[i]=j;
po=i;//更新po的位置
}
}
}
int main()
{
while(cin>>s>>t)
{
exkmp(s,t);
for(int i=0;i<t.size();i++)
cout<<Next[i]<<endl;
for(int i=0;i<s.size();i++)
cout<<exnext[i]<<endl;
}
return 0;
}
一个节点和其所有孩子都有相同的前缀,即该节点对应的字符串,或者说从根节点出发到该节点的路径,组成的子串
各种操作均为O(n)
常见用法:
1.字符串匹配
2.前缀查询
3.求连续异或和
#include
using namespace std;
const int N=100005;
int tot=0; //tot表示下个结点的编号
int trie[N][26]; //存贮字典树,tire[rt][x],表示其下一个结点的编号,其中rt表示父节点编号,x表示字母
int sum[N]; //保存前缀出现次数
void insert(string ch) { //类似链式前向星的思想
int rt = 0;
int len=ch.size();
for (int i = 0; i < len; i++) {
if (trie[rt][ch[i] - 'a'] == 0) //当前字母未出现过
trie[rt][ch[i] - 'a'] = ++tot;
sum[trie[rt][ch[i]-'a']]++;
rt = trie[rt][ch[i] - 'a'];
}
}
bool find(string s) //查询s是否存在
{
int rt=0;
int len=s.size();
for(int i=0;i<len;i++)
{
if(trie[rt][s[i]-'a']==0) return false; //在rt后没有以s[i]开头的字符串
rt=trie[rt][s[i]-'a']; //若有,则继续向下
}
return true;
}
int search(string s) //查询以s为前缀的串个个数
{
int rt=0;
int len=s.size();
for(int i=0;i<len;i++)
{
if(trie[rt][s[i]-'a']==0) return 0;
rt=trie[rt][s[i]-'a'];
}
return sum[rt];
}
int main()
{
string a;
while(cin>>a)
{
insert(a);
cout<<find(a)<<endl;
cout<<search(a)<<endl;
}
return 0;
}
用字典树保存一个数的二进制形式(只由01构成),若想要异或值大,一定要越靠前的数异或后为1,即尽可能使权值较大的数异或后为1
常见用法:
求各种与异或相关的操作
求区间最大异或值
#include
#define maxn 1005
#define inf 0x3f3f3f3f
using namespace std;
typedef long long ll;
int a[maxn];
int num[32*maxn][2];
int node[32*maxn][2];
int val[32*maxn];
int sum,ans,l,r,anss,s;
void init(){
sum=1;
ans=-inf;
memset(num,0,sizeof num);
memset(node,0,sizeof node);
memset(val,0,sizeof val);
}
void change1(int m,int x){
int pos=0;
for(int i=30;i>=0;i--){
int j=x>>i&1;
num[pos][j]+=m;
if(node[pos][j]) pos=node[pos][j];
else{
memset(node[sum],0,sizeof node[sum]);
node[pos][j]=sum++;
pos=node[pos][j];
}
}
val[pos]=x;
}
int search1(int L,int R,int x){
int pos=0;
int w=0;
for(int i=30;i>=0;i--){
int j=x>>i&1;
if(num[pos][!j]){
w+=1<<i;
pos=node[pos][!j];
}
else pos=node[pos][j];
}
if(w>ans) ans=w,l=L,r=R,anss=val[pos];
}
int main(){
int t,n;
cin>>t;
while(t--){
init();
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i],change1(1,a[i]);
for(int i=1;i<=n;i++){
s=0;
for(int j=i;j<=n;j++){
s+=a[j];
change1(-1,a[j]);
int w=search1(i,j,s);
}
for(int j=i;j<=n;j++) change1(1,a[j]);
}
cout<<l<<" "<<r<<" "<<anss<<" "<<ans<<endl;
}
}
结合KMP与trie,相当于在trie上构建了一个next数组,保存每个位置的失配后调整位置
用于多字符串匹配,如在匹配cat时失配,但存在另一个单词cart,则失配指针指向前缀ca,避免重复计算
#include
using namespace std;
const int MAXN=1000005;
const int MAXC=26;
struct AC_Automaton {
int trie[MAXN][MAXC], fail[MAXN], sta[MAXN],Q[MAXN];
int tot;
void init() {
memset(trie, 255, sizeof(trie));
memset(fail, 0, sizeof(fail));
tot = 0;
memset(sta, 0, sizeof(sta));
}
void insert(string ch) { //插入字符串到字典树中
int rt = 0, l = ch.size();
for (int i = 0; i < l; i++)
{
if (trie[rt][ch[i] - 'a'] == -1) trie[rt][ch[i] - 'a']= ++tot;
rt = trie[rt][ch[i] - 'a'];
}
sta[rt]++;
}
void build() //构建fail指针数组,相当于next数组,利用bfs求
{
queue<int>Q;
for(int i=0;i<MAXC;i++) //初始化
if(trie[0][i]==-1)
trie[0][i]=0; //将不存在的点指向根节点
else
Q.push(trie[0][i]); //将与根节点直接相连的点入队
while(!Q.empty())
{
int rt=Q.front();
Q.pop();
for(int i=0;i<MAXC;i++)
{
if(trie[rt][i]==-1) //某一点无后续节点,将其连向失配指针所在位置
trie[rt][i]=trie[fail[rt]][i];
else //有后续节点
{
fail[trie[rt][i]]=trie[fail[rt]][i]; //其失配指针是从其父亲失配指针指向位置向后搜索i,若有则连接,若无连向根
Q.push(trie[rt][i]);
}
}
}
}
int solve(string ch) { //求字典树中有多少字符串出现在ch中
int ret = 0, rt = 0, l = ch.size();
for (int i = 0; i < l; i++) {
rt = trie[rt][ch[i] - 'a'];
int tmrt = rt;
while (tmrt) {
ret += sta[tmrt];
sta[tmrt] = 0;
tmrt = fail[tmrt];
}
}
return ret;
}
}T;
int main()
{
std::ios::sync_with_stdio(false);
int n;
string a;
cin>>n;
T.init();
while(n--)
{
cin>>a;
T.insert(a);
}
T.build();
string t;
cin>>t;
cout<<T.solve(t)<<endl;
return 0;
}
Trie图上每个点都是一个状态,在AC自动机的状态相互装化,可以实现动态规划
1.求模式串在原串中出现的次数:(所有模式串不同)
构建Trie树时,在每个串结尾节点进行标记,在AC自动机上匹配时,每遇到一次结尾节点即表示成功匹配,ans++
2.求模式串在原串中出现次数,若模式串B是模式串A的子串,则只记录A:
构建Trie树时,在每个串结尾节点进行标记,在AC自动机上匹配时,对经过的子串模式串消除标记,其余与之前相同
3.求原串中不包含任何模式串的串的种类数:
对所有模式串构建AC自动机,模式串的终止的都是非法的,不可经过
dp[i][j]表示长度为i,状态为j的字符串的种类数,枚举所有字符进行状态匹配,答案即为sum(dp[m][i])
若m较小,n较大,可以考虑用矩阵乘法加速dp
4.求一个长度最短的串使得它包含所有模式串:
对所有模式串建立AC自动机,若n很小,可用状态压缩dp
二进制状态j&2^k表示从根节点到该结点的路径上有第k个模式串 dp[i][j]表示状态i,二进制状态j的最短长度,初始化dp[i][j]=0
在Trie上求(0,0)到(i,2n-1)点的最短路,答案即是dp[i][2-1]
以最长回文为前缀构建的字典树,用回文串代替字典树中的前缀
1.求串S前缀0~i内本质不同回文串的个数(两个串长度不同或者长度相同且至少有一个字符不同便是本质不同)
2.求串S内每一个本质不同回文串出现的次数
3.求串S内回文串的个数(其实就是1和2结合起来)
4.求以下标i结尾的回文串的个数
const int MAXN = 100005 ;
const int N = 26 ;
struct Palindromic_Tree {
int next[MAXN][N] ;//next指针,next指针和字典树类似,指向的串为当前串两端加上同一个字符构成
int fail[MAXN] ;//fail指针,失配后跳转到fail指针指向的节点
int cnt[MAXN] ; //表示节点i表示的本质不同的串的个数(建树时求出的不是完全的,最后count()函数跑一遍以后才是正确的)
int num[MAXN] ; //表示以节点i表示的最长回文串的最右端点为回文串结尾的回文串个数
int len[MAXN] ;//len[i]表示节点i表示的回文串的长度(一个节点表示一个回文串)
int S[MAXN] ;//存放添加的字符
int last ;//指向新添加一个字母后所形成的最长回文串表示的节点。
int n ;//表示添加的字符个数。
int p ;//表示添加的节点个数。遍历所有结点要从[2~p-1]
int newnode ( int l ) {//新建节点
for ( int i = 0 ; i < N ; ++ i ) next[p][i] = 0 ;
cnt[p] = 0 ;
num[p] = 0 ;
len[p] = l ;
return p ++ ;
}
void init () {//初始化
p = 0 ;
newnode ( 0 ) ;
newnode ( -1 ) ;
last = 0 ;
n = 0 ;
S[n] = -1 ;//开头放一个字符集中没有的字符,减少特判
fail[0] = 1 ;
}
int get_fail ( int x ) {//和KMP一样,失配后找一个尽量最长的
while ( S[n - len[x] - 1] != S[n] ) x = fail[x] ;
return x ;
}
void add ( int c ) {
c -= 'a' ;
S[++ n] = c ;
int cur = get_fail ( last ) ;//通过上一个回文串找这个回文串的匹配位置
if ( !next[cur][c] ) {//如果这个回文串没有出现过,说明出现了一个新的本质不同的回文串
int now = newnode ( len[cur] + 2 ) ;//新建节点
fail[now] = next[get_fail ( fail[cur] )][c] ;//和AC自动机一样建立fail指针,以便失配后跳转
next[cur][c] = now ;
num[now] = num[fail[now]] + 1 ;
}
last = next[cur][c] ;
cnt[last] ++ ;
}
void count () {
for ( int i = p - 1 ; i >= 0 ; -- i ) cnt[fail[i]] += cnt[i] ;
//父亲累加儿子的cnt,因为如果fail[v]=u,则u一定是v的子回文串!
}
} ;
计算一个字符串所有后缀经过字典排序后的起始下标结果
sa数组(后缀数组): 将S的n个后缀从小到大进行排序之后把排好序的后缀的开头位置顺次放入SA
rk数组(名次数组): 名次数组Rank[i]保存的是Suffix(i)在所有后缀中从小到大排列的“名次”
height数组:定义height[i]=suffix(sa[i-1])和suffix(sa[i])的最长公共前缀,也就是排名相邻的两个后缀的最长公共前缀。那么对于j和k,不妨设rank[j]< rank[k],则有以下性质:
suffix(j)和suffix(k)的最长公共前缀为height[rank[j]+1],height[rank[j]+2],height[rank[j]+3],……,height[rank[k]]中的最小值。
1.可重叠最长重复子串:给定一个字符串,求最长重复子串,这两个子串可重叠
重复子串即是两后缀的公共前缀,最长重复子串,等价于两后缀的最长公共前缀的最大值
只需计算height数组,再比较最大值,就是最长重复子串的长度
2.不可重叠最长重复子串:
直接用height数组不能保证两后缀不重叠,可二分答案,进行判定
问题转化为:原串中是否有长度为k的重复子串,按height数组把后缀分组,每组height值都>=k。
若存在至少一组,SAmax - SAmin >=k,则存在长度为k的重复子串
3.最长k次重复子串:求至少出现k次的最长重复子串,且k个子串可以重叠
先二分答案,将后缀分成如果组,判断有没有一个组的后缀个数不小于k,若有,则存在k个相同的子串满足条件
反之,不存在
4.不同子串的个数:
即对n-sa[i]+1-height[i]求和
1.最长公共子串:给定两个串,求这两个串的最长公共子串
先将两个串拼接在一起,形成A&B的形式,$为分隔符,小于所有字母。再求出新串的后缀数组及height
求排名相邻,原来不在同一个字符串的height值的最大值
2.长度小于k的公共子串个数:求两个串中,长度不小于k的公共子串个数,可以相同
先将两个串拼接,按height值分组后,统计每组中后缀之间的最长公共前缀和
扫描一遍,没遇到一个b的后缀就统计与前面a的后缀能产生多少个长度不小于k的公共子串
a的后缀用一个单调栈维护,再对a的后缀与前面b的后缀也做同样的计算
1.其它串没有的子串:问a串中有多少种字符串集合B中没有的连续子串
先将a串和其它子串拼接,求一遍总的子串个数,再减去a串与集合B相连的子串个数
2.多串的最长公共子串:
将n个字符串拼接,二分答案,将后缀按height值分成若干组,判断是否存在一组后缀属于n个字符串
3.不少于k个串的最长子串
将n个字符串拼接,求后缀数组,二分答案,将后缀按height值分成若干组
判断每组的后缀是否出现在不小于k个的原串中
4.每个串中至少出现2次且不重叠的子串个数:
将所有串拼接,二分答案,按height值分组,若某一组存在每个串至少出现2次,则满足
5.出现或反转后出现在每个字符串的最长子串:
先将每个字符串反转,在将反转后的与原来的一起全部拼接,求后缀数组。
二分答案,将后缀分组,判断时看是否有一组后缀在每个原来的串或反转后的串中出现
构建后缀数组和height,倍增法O(nlogn),求出现次数大于等于k次的最长子串长度
#include
using namespace std;
const int MAXN = 200010;
int s[MAXN]; // s 数组保存了字符串中的每个元素值,除最后一个元素外,每个元素的值在 1..m 之间,最后一个元素的值为 0
int wa[MAXN], wb[MAXN], wc[MAXN], wd[MAXN]; // 这 4 个数组是后缀数组计算时的临时变量,无实际意义
int sa[MAXN]; // sa[i] 保存第 i 小的后缀在字符串中的开始下标,i 取值范围为 0..n-1
int cmp(int *r, int a, int b, int l) {
return r[a] == r[b] && r[a + l] == r[b + l];
}
void getSA(int *r, int *sa, int n, int m) { // n 为字符串的长度,m 为字符最大值
int i, j, p, *x = wa, *y = wb;
for (i = 0; i < m; ++i) wd[i] = 0;
for (i = 0; i < n; ++i) wd[x[i] = r[i]]++;
for (i = 1; i < m; ++i) wd[i] += wd[i - 1];
for (i = n - 1; i >= 0; --i) sa[--wd[x[i]]] = i;
for (j = 1, p = 1; p < n; j *= 2, m = p) {
for (p = 0, i = n - j; i < n; ++i) y[p++] = i;
for (i = 0; i < n; ++i) if (sa[i] >= j) y[p++] = sa[i] - j;
for (i = 0; i < n; ++i) wc[i] = x[y[i]];
for (i = 0; i < m; ++i) wd[i] = 0;
for (i = 0; i < n; ++i) wd[wc[i]]++;
for (i = 1; i < m; ++i) wd[i] += wd[i - 1];
for (i = n - 1; i >= 0; --i) sa[--wd[wc[i]]] = y[i];
for (swap(x, y), p = 1, x[sa[0]] = 0, i = 1; i < n; ++i)
x[sa[i]] = cmp(y, sa[i - 1], sa[i], j) ? p - 1 : p++;
}
return;
}
int n; //字符串长度
int Rank[MAXN]; // Rank[i] 表示从下标 i 开始的后缀的排名,值为 1..n
int height[MAXN]; // 下标范围为 1..n,height[1] = 0,表示suffix(sa[i-1])和suffix(sa[i])的最长公共前缀,即排名相邻的两个后缀的最长公共前缀
void getHeight(int *r,int *sa,int n) {
int i, j, k = 0;
for (i = 1; i <= n; ++i) Rank[sa[i]] = i;
for (i = 0; i < n; i++) {
if (k) k--;
int j = sa[Rank[i] - 1];
while (r[i + k] == r[j + k]) k++;
height[Rank[i]] = k;
}
return;
}
int lcp[MAXN][30]; //存储lcp
void init_RMQ(int n) //初始化rmq
{
for(int i=0;i<n;i++) lcp[i][0]=height[i];
for(int j=1;(1<<j)<=n;j++)
for(int i=0;i+(1<<j)<=n;i++)
lcp[i][j]=min(lcp[i][j-1],lcp[i+(1<<(j-1))][j-1]);
}
int RMQ(int l,int r) //查询l~r的lcp
{
int k=0;
while((1<<(k+1))<=r-l+1) k++;
int ans=min(lcp[l][k],lcp[r-(1<<k)+1][k]);
return ans;
}
int ask(int l,int r) //询问l~r的lcp,含边界处理
{
if(l==r) return n-sa[r];
return RMQ(l+1,r);
}
bool check(int nk,int K) //将height分组判断
{
int cnt=1;
for(int i=1;i<=n;i++)
{
if(height[i]>=nk) //统计长度大于nk的子串个数
{
cnt++;
if(cnt>=K) return true; //出现次数大于等于K,找到答案
}
else
cnt=1;
}
return false;
}
int main()
{
int K;
cin>>n>>K;
for(int i=0;i<n;i++)
cin>>s[i];
s[n]=0; //必须要加!!,将s最后一位置为一个最小值
getSA(s,sa,n+1,20000); //!!!必须是n+1!!!
getHeight(s,sa,n);
init_RMQ(n+1); //注意是n+1!!!!
/* //求出现次数大于等于k的最长子串长度
int l=0,r=n;
int ans=0;
while(l<=r)
{
int mid=(l+r)>>1; //二分枚举子串长度
if(check(mid,K))
{
ans=mid;
l=mid+1;
}
else r=mid-1;
}
cout<
/* //求出现次数在[l,r]区间中的子串个数
for(int i=1;i+l-1<=n;i++)
{
ans+=ask(i,i+l-1); //求出所有长度为l的区间的贡献
if(i-1>0) ans-=ask(i-1,i+l-1); //减去相邻区间重复贡献值
if(i+r<=n) ans-=ask(i,i+r); //减去长度为r+1区间的贡献值
if(i-1>0 && i+r<=n) ans+=ask(i-1,i+r); //容斥处理,加上多减去的部分
}
printf("%lld\n",ans);
*/
return 0;
}
求出现k次(或大于k次,或k1~k2次)的子串个数
#include
using namespace std;
typedef long long ll;
const int maxn = 2e6+3;
int root,last;
int cnt;
int l;
int sv[maxn*2];
struct query
{
int a;
ll ans;
}qu[maxn];
struct sam_node
{
int fa,son[26];
int len;
void init(int _len)
{
len = _len;
fa = -1;
memset(son,-1,sizeof(son));
}
}t[maxn*2];
void init()
{
cnt = 0;
root = last = 0;
t[cnt].init(0);
}
void extend(int w)
{
int p = last;
int np = ++cnt;
t[cnt].init(t[p].len+1);
sv[l] = np;
int q, nq;
while(p != -1 && t[p].son[w] == -1)
{
t[p].son[w] = np;
p = t[p].fa;
}
if(p == -1) t[np].fa = root;
else
{
q = t[p].son[w];
if (t[p].len+1 == t[q].len) t[np].fa=q;
else
{
nq = ++cnt;
t[nq].init(0);
t[nq] = t[q];
t[nq].len = t[p].len+1;
t[q].fa = nq;
t[np].fa = nq;
while(p!=-1&&t[p].son[w]==q)
{
t[p].son[w] = nq;
p = t[p].fa;
}
}
}
last = np;
}
int w[maxn], r[maxn*2];
void topo()
{
for(int i = 0; i <= l; ++i) w[i] = 0;
for(int i = 1; i <= cnt; ++i) w[t[i].len]++;
for(int i = 1; i <= l; ++i) w[i] += w[i-1];
for(int i = cnt; i >= 1; --i) r[w[t[i].len]--] = i;
r[0] = 0;
}
int dp[maxn*2];
char s[maxn];
int main()
{
int n, k, p;
while(~scanf("%s",&s))
{
int L,R;
scanf("%d%d",&L,&R);
int tl = strlen(s);
l = 0;
init();
for(int i = 0; i < tl; ++i)
{
++l;
extend(s[i]-'A');
}
for(int i = 0; i <= cnt; ++i) dp[i] = 0;
topo();
p = root;
for(int i = 0; i < l; ++i)
{
p = t[p].son[s[i]-'A'];
dp[p]++;
}
for(int i = cnt; i >= 1; --i)
{
p = r[i];
if(t[p].fa != -1) dp[t[p].fa] += dp[p];
}
ll ans1 = 0, ans2 = 0;
for(int i = 1; i <= cnt; ++i)
if(dp[i] >= L) //L表示下界
ans1 += t[i].len-t[t[i].fa].len;
for(int i = 1; i <= cnt; ++i)
if(dp[i] >= R+1) //R表示上界
ans2 += t[i].len-t[t[i].fa].len;
printf("%lld\n", ans1-ans2); //求出现次数为L~R之间的子串的个数
}
return 0;
}