SAM是处理单个字符串的首选算法,若是多个字符串但是又符合SAM出题的题型,考虑广义SAM
1.伪广义SAM
大概有两种做法
1.在各个字符串中间加上一些奇怪的字符(或者让last变成初始点),然后按照普通SAM做即可
2.对一个字符串构建SAM(一般选最短的),然后用其他的字符串去在这个SAM上跑,打上标记后经过一系列操作去得到正确答案
并没有想到用广义SAM怎么做
考了对一个串建立SAM
对于另一个串,在SAM上跑,维护当前最长子串,因为相当于长度为1~最长子串所有后缀都出现过一次
每经过一个点,先加上最长子串-此节点最短子串的个数,然后在他的parent tree的父亲节点打上标记递推即可
void get(char *s){
int len=strlen(s+1);
int now=1;
long long nowl=0;
for(int i=1;i<=len;i++){
while(now!=1&&!mes[now].nxt[s[i]-'a']) now=mes[now].fa,nowl=mes[now].len;
if(mes[now].nxt[s[i]-'a']){
now=mes[now].nxt[s[i]-'a'],nowl++;
ans+=(nowl-mes[mes[now].fa].len)*mes[now].cs1,mes[mes[now].fa].cs2++;
//出现过的子串先处理这个节点表示的一部分
}
}
}
void dfs(int x,int op){
for(int i=0;i<tu[x].size();i++){
int p=tu[x][i];
dfs(p,op);
if(op==1) mes[x].cs1+=mes[p].cs1;
else mes[x].cs2+=mes[p].cs2;
}
}
void get_ans(char *s){
for(int i=2;i<=sz;i++) tu[mes[i].fa].push_back(i);
dfs(1,1);//先求出每个子串第一次出现次数
get(s);
dfs(1,2);
for(int i=2;i<=sz;i++) ans+=1ll*(mes[i].len-mes[mes[i].fa].len)*mes[i].cs1*mes[i].cs2;
printf("%lld",ans);
}
2.真正的广义SAM
考虑trie树的优秀性质
他可以帮我们合并子串的部分相同前缀,降低时间复杂度
他可以帮我们更新字符串插入SAM的顺序,从而维护了SAM的性质
因为SAM插入是按照长度单调递增的顺序插入的,所以需要按照字典树bfs的顺序来插入
每次插入时las为字典树上这个节点的父亲,保证了串的完整性
然后每次多个串有相同的字符能够第一时间在SAM上建立关系,保证了时间复杂度
先建出trie树,然后就化简为单个串本质不同子串个数了
注意的是,每个节点变化直接用trie树的就行了(保证las的对应性)
struct GSAM{
SAM_node mes[2001000];
int dic[2000100][30];
int trie_sz;
void init(){
trie_sz=sam_sz=1;
mes[1].fa=mes[1].len=0;
}
void insert_trie(char *s){
int len=strlen(s+1),now=1;
for(int i=1;i<=len;i++){
int ty=s[i]-'a';
if(!dic[now][ty]) dic[now][ty]=++trie_sz;
now=dic[now][ty];
}
}
void insert_sam(int las,int ch){
int now=dic[las][ch];
mes[now].len=mes[las].len+1;
while(las&&!mes[las].nxt[ch]) mes[las].nxt[ch]=now,las=mes[las].fa;
if(!las) mes[now].fa=1;
else{
int to=mes[las].nxt[ch];
if(mes[to].len==mes[las].len+1) mes[now].fa=to;
else{
int np=++trie_sz;
mes[np]=mes[to],mes[np].len=mes[las].len+1;
while(las&&mes[las].nxt[ch]==to) mes[las].nxt[ch]=np,las=mes[las].fa;
mes[to].fa=mes[now].fa=np;
}
}
}
void get_mes(){
queue<pair<int,int> >q;
for(int i=0;i<26;i++){
if(dic[1][i]) q.push({1,i});
}
while(!q.empty()){
int ch=q.front().second,las=q.front().first;
int x=dic[las][ch];
q.pop();
insert_sam(las,ch);
for(int i=0;i<26;i++){
if(dic[x][i]) q.push({x,i});
}
}
}
void get_ans(){
long long ans=0;
for(int i=2;i<=trie_sz;i++) ans+=mes[i].len-mes[mes[i].fa].len;
printf("%lld",ans);
}
};
树上求本质不同字符串
先要考虑到两点
1.将无根树的每一个叶子节点作为根,然后求每个节点到根的前缀子串,一定能够遍历所有的子串
2.不能每次新添加子串,记录上一个添加到的位置,在后面添加一个字符即可
这样就转化为了上一题