浅谈后缀自动机

后缀自动机

自动机

首先什么是自动机

我们大多用的是\(DFA\),也就是有限状态自动机

整个自动机是由一些边和点组成的,边上的权为字符

简单理解就是输入一个字符串如果是我们想要接受的,那这个字符串就会按顺序遍历图,并最后会在终止节点停下,是为接受

当然,也有一些字符串无法被接受,比如AC自动机就是接受模式串

后缀相关

我们将以\(i\)为结尾的所有子串建一棵\(Tire\)

很明显这样的\(Tire\)点数是\(n^2\)的,同时它可以接受所有子串的功能

而后缀自动机就是最小化上述的自动机

为了缩小点数,我们可以对一些具有相同性质的点缩点

定义\(R_S\)为子串\(S\)在原串中所有出现位置的右端点组成的集合,我们把相同\(R\)的子串缩成一个点

然后探究一下这样做的正确性与性质

首先对于\(R_X\)\(R_Y\),如果两者有交集,则一定是包含或被包含的关系

考虑反证,如果存在\(R_X,R_Y\)有交集,在交集的位置,我们假设\(|X|>|Y|\),则\(Y\)\(X\)的一个子串

对于\(pos\in R_X,\notin R_Y\),其\(|Y|\)一定能在\(pos\)位置匹配到,则\(R_Y\subseteq R_X\)

由此,我们定义\(Link\)表示当前节点\(i\)代表\(R\),\(Link_i\)\(R\subseteq R'\)最小的\(R'\),也就是从\(R'\)这里选一些子集

注意到\(Link\)形成的是一个树的结构,同时每次连边会划分集合至少两个,因此点数保证一定是\(2*n\)左右

注意区分自动机上的连边和\(Link\)

自动机的连边是在当前子串\(S\)上加一个字符\(ch\)\(S+ch\)能匹配到所有的右端点,相当于在后面加字符

\(Link\)边是为了放大\(R\),相当于在前缀删字符最多删多少,同时注意到一个节点代表的是一段连续的区间,因为删字符得到的前缀一定是连续的,如果设\(Len_i\)表示\(i\)点代表字符串的最长长度,那么\(i\)代表的区间为\([Len_{link_i}+1,Len_i]\)

具体实现

后缀自动机的实现是在线的

记当前末尾位置为\(Lp\),字符为\(ch\)

具体的我们考虑记录\(Last\)表示之前自动机末尾停下的节点,\(cur\)为当前末尾的节点,\(cur\)肯定是连在\(Last\)的后面,边为\(ch\),同时\(Len_{cur}=Len_{Last}+1\)

考虑跳\(Last\)字符串的前缀也就是跳\(Link\)链,假设现在跳到了节点\(P\)如果\(P\)没有\(ch\)边,则直接给它连到\(cur\)

找到第一个有\(ch\)边的\(P\),走\(ch\)边后为\(Q\),如果没找到直接\(Link_{cur}=0\)

考虑如果\(Len_Q=Len_P+1\),则\(P\)\(ch\)等同于\(Q\)就可以了,\(Link_{cur}=Q\),同时\(Q\)\(Link\)链上的\(R\)全部插一个\(Lp\)(没必要实现)

如果\(Len_Q>Len_P+1\),则\(Q\)对应的子串比\(P+ch\)更长,即在\(Link\)树上\(P+ch\)\(Q\)的父亲,我们复制\(Q\)\(Clone\),直接\(Link_Q=Link_{cur}=Clone\),同时我们继续爬\(Last\)\(Link\)链,将所有指向\(Q\)的全部指向\(Clone\)

struct SAM{
	SAM_node Tree[MAXN*2]; 
	int cnt_node;
	int Last;
	void Build()
	{
		cnt_node=0;
		Last=0;
		Tree[0].Link=-1;
		Tree[0].Len=0;
		return;
	}
	void Insert(char s)
	{
		int p=Last;
		int cur=++cnt_node;
		p=Tree[Last].Link;
		Tree[Last].Next[s-'a']=cur;
		Tree[cur].Len=Tree[Last].Len+1;
		while((p!=-1)&&(!Tree[p].Next[s-'a']))
		{
			Tree[p].Next[s-'a']=cur;
			p=Tree[p].Link;
		}		
		if(p==-1)
		{
			Tree[cur].Link=0;
		}
		else
		{
			int q=Tree[p].Next[s-'a'];
			if(Tree[q].Len==Tree[p].Len+1)
			{
				Tree[cur].Link=q;
			}
			else
			{
				int clone=++cnt_node;
				Tree[clone]=Tree[q];
				Tree[clone].Len=Tree[p].Len+1;
				Tree[q].Link=clone;
				Tree[cur].Link=clone;
				while((p!=-1)&&(Tree[p].Next[s-'a']==q))
				{
					Tree[p].Next[s-'a']=clone;
					p=Tree[p].Link;
				}
			}
		}
		Last=cur;
	}
}sam;

一些应用

首先明确一些性质

根节点到每个点的所有路径拼起来就是这个点代表的字符串集合

\(R\)的大小就是这个点出现节点的次数,同时\([Len_{Link_i}+1,Len_i]\)\(i\)代表节点的长度区间

不同字符串的个数及总长度

考虑每个点的贡献分别为\(Len_i-Len_{Link_i}\)\(\dfrac{(Len_i+1+Len_{Link_i})(Len_i-Len_{Link_i})}{2}\)

出现次数

每个节点的出现次数就是\(|R|\),我们在插入的时候就在终止节点插一个元素,维护即可

K子串

预处理一下当前节点为起点有多少条路径然后贪心即可

#include <bits/stdc++.h>
const int MAXN = 1e6 + 5;
using namespace std;
struct SAM_node {
    int Len;
    int Next[26];
    int Link;
    int num;
};
struct SAM {

    SAM_node Tree[MAXN * 2];
    int cnt_node;
    int T;
    vector<int>g[MAXN * 2];
    vector<int>G[MAXN * 2];
    int Rd[MAXN * 2];
    long long Dp[MAXN * 2];
    int Last;
    void Build() {
        cnt_node = 0;
        Last = 0;
        Tree[0].Link = -1;
        Tree[0].Len = 0;
        return;
    }
    void Insert(char s) {
        int p = Last;
        int cur = ++cnt_node;
        p = Tree[Last].Link;
        Tree[Last].Next[s - 'a'] = cur;
        Tree[cur].Len = Tree[Last].Len + 1;

        while ((p != -1) && (!Tree[p].Next[s - 'a'])) {
            Tree[p].Next[s - 'a'] = cur;
            p = Tree[p].Link;
        }

        if (p == -1) {
            Tree[cur].Link = 0;
        } else {
            int q = Tree[p].Next[s - 'a'];

            if (Tree[q].Len == Tree[p].Len + 1) {
                Tree[cur].Link = q;
            } else {
                int clone = ++cnt_node;
                Tree[clone] = Tree[q];
                Tree[clone].num = 0;
                Tree[clone].Len = Tree[p].Len + 1;
                Tree[q].Link = clone;
                Tree[cur].Link = clone;

                while ((p != -1) && (Tree[p].Next[s - 'a'] == q)) {
                    Tree[p].Next[s - 'a'] = clone;
                    p = Tree[p].Link;
                }
            }
        }

        Last = cur;
        Tree[cur].num++;
    }
    void Link_Build() {
        for (int i = 0; i <= cnt_node; i++) {
            if (Tree[i].Link != -1) {
                g[Tree[i].Link].push_back(i);
            }

            for (int j = 0; j <= 25; j++) {
                if (!Tree[i].Next[j]) {
                    continue;
                }

                G[Tree[i].Next[j]].push_back(i);
                Rd[i]++;
            }
        }
    }
    void dfs(int x) {
        for (int i = 0; i < g[x].size(); i++) {
            int v = g[x][i];
            dfs(v);
            Tree[x].num += Tree[v].num;
        }
    }
    void Topsort() {
        queue<int>q;

        for (int i = 1; i <= cnt_node; i++) {
            if (!Rd[i]) {

                q.push(i);
            }

            if (!T) {
                Dp[i] = 1;
            } else {
                Dp[i] = Tree[i].num;
            }
        }

        while (q.size()) {
            //      printf(">?");
            int temp = q.front();
            q.pop();

            for (int i = 0; i < G[temp].size(); i++) {
                int v = G[temp][i];
                Rd[v]--;
                Dp[v] += Dp[temp];

                if (Rd[v] == 0) {
                    q.push(v);
                }
            }
        }
    }
    void Rank(int k) {
        int p;

        for (int i = 0; i <= 25; i++) {
            if (!Tree[0].Next[i]) {
                continue;
            }

            int Pox = Tree[0].Next[i];

            if (Dp[Pox] < k) {
                k -= Dp[Pox];
            } else {
                printf("%c", i + 'a');
                p = Pox;
                break;
            }
        }

        while (1) {
            //  printf("???");
            int Kp;

            if (!T) {
                Kp = 1;
            } else {
                Kp = Tree[p].num;
            }

            if (k <= Kp) {
                break;
            } else {
                k -= Kp;
            }

            for (int i = 0; i <= 25; i++) {
                if (!Tree[p].Next[i]) {
                    continue;
                }

                int Pox = Tree[p].Next[i];

                if (Dp[Pox] < k) {
                    k -= Dp[Pox];
                } else {
                    printf("%c", i + 'a');
                    p = Pox;
                    break;
                }
            }
        }

    }
} sam;
string S;
int T;
int k;
int main() {
    cin >> S;
    sam.Build();

    for (int i = 0; i < S.size(); i++) {
        sam.Insert(S[i]);
    }

    scanf("%d %d", &T, &k);
    sam.T = T;
    sam.Link_Build();
    sam.dfs(0);
    sam.Topsort();
    sam.Rank(k);
}

最小循环移位

首先如果存在循环位移,最小循环节长度应该为\(N-Next[N]\)

证明考虑匹配的是\([1,Next[N]]\)段,你将这一段移一下去正好相等

这启示我们直接建\(S+S\)的后缀自动机,在上面找一个与原串相等且右端点最小的即可(相比于KMP空间有点大)

#include<cstdio>
#include<algorithm>
#include<vector>
#include<iostream>
#include<cstring>
const int MAXN=1e6+5;
using namespace std;
struct SAM_node{
	int Len;
	int Next[26];
	int Link;
    int min_num;
};
int Lit;
struct SAM{
	SAM_node Tree[MAXN*2]; 
    vector<int>g[MAXN*2];
	int cnt_node;
	int Last;
	void Build()
	{
		cnt_node=0;
		Last=0;
		Tree[0].Link=-1;
		Tree[0].Len=0;
		return;
	}
    int New()
    {
        ++cnt_node;
        for(int i=0;i<26;i++)
        {
            Tree[cnt_node].Next[i]=0;
        }
        Tree[cnt_node].Link=0;
        Tree[cnt_node].min_num=0x3f3f3f3f;
        Tree[cnt_node].Len=0;
        return cnt_node;
    }
	void Insert(char s,int Pos)
	{
		int p=Last;
		int cur=New();
		p=Tree[Last].Link;
		Tree[Last].Next[s-'a']=cur;
		Tree[cur].Len=Tree[Last].Len+1;
		while((p!=-1)&&(!Tree[p].Next[s-'a']))
		{
			Tree[p].Next[s-'a']=cur;
			p=Tree[p].Link;
		}		
		if(p==-1)
		{
			Tree[cur].Link=0;
		}
		else
		{
			int q=Tree[p].Next[s-'a'];
			if(Tree[q].Len==Tree[p].Len+1)
			{
				Tree[cur].Link=q;
			}
			else
			{
				int clone=++cnt_node;
				for(int i=0;i<26;i++)
                {
                    Tree[clone].Next[i]=Tree[q].Next[i];
                }
                Tree[clone].Link=Tree[q].Link;
				Tree[clone].min_num=0x3f3f3f3f;
				Tree[clone].Len=Tree[p].Len+1;
				Tree[q].Link=clone;
				Tree[cur].Link=clone;
				while((p!=-1)&&(Tree[p].Next[s-'a']==q))
				{
					Tree[p].Next[s-'a']=clone;
					p=Tree[p].Link;
				}
			}
		}
		Last=cur;
		if(Pos>Lit)
        {
            Tree[cur].min_num=min(Tree[cur].min_num,Pos);
        }
	}
    void Link_Build()
	{
        for(int i=0;i<=cnt_node;i++)
        {
            g[i].clear();
        }
		for(int i=0;i<=cnt_node;i++)
		{
			if(Tree[i].Link!=-1)
			{
				g[Tree[i].Link].push_back(i);
            }
		}
	}
	void dfs(int x)
	{
		for(int i=0;i<g[x].size();i++)
		{
			int v=g[x][i];
			dfs(v);
			Tree[x].min_num=min(Tree[v].min_num,Tree[x].min_num);
		}
	}
    int Find(string S)
    {
        int P=0;
        for(int i=0;i<S.size();i++)
        {
            P=Tree[P].Next[S[i]-'a'];
        }
        return Tree[P].min_num;
    }
}sam;
char SS[MAXN];
string S;
int T;
int k;
int main()
{
    while(1)
    {
        cin >> SS;
        S = string(SS, SS+strlen(SS));
        if(S[0]=='.')
        {
            break;
        }
        Lit=S.size();
        sam.cnt_node=0;
        sam.Build();
        for(int i=0;i<S.size();i++)
        {
            sam.Insert(S[i],i+1);
        }  
        for(int i=0;i<S.size();i++)
        {
            sam.Insert(S[i],(i+1)+(int)S.size());
        }
        sam.Link_Build();
        sam.dfs(0);
        int Pd=sam.Find(S);
        if(Pd==0x3f3f3f3f)
        {
            printf("1\n");
        }
        else
        {
            int Lp=(Pd-(S.size()));
            printf("%d\n",int((S.size())/Lp));
        }
    }
 } 

第一次出现的位置

很明显维护每一个节点\(R\)集合的最小值

所有出现的位置

维护对应节点的\(R\)即可,大概要用启发式合并?

其实直接遍历子树就行了

最短的没有出现的字符串

一个很zz的问题,就是一层一层的找到第一个连边没有覆盖到所有字符集的节点

两个字符串的最长公共子串

假设有两个串为\(S,T\)

我们对\(S\)\(SAM\),考虑\(T\)的前\(i\)为的匹配的最长后缀,答案明显显然为最大值

然后考虑优化这个过程,把\(T\)\(i\)的字符串放进\(SAM\)匹配最长后缀,设当前匹配到\(p\),长度为\(l\)

添加\(T_{i+1}\),如果\(p\)有连边就直接走,否则跳\(Link\)链找第一个满足的

多个字符串的最长公共子串

考虑沿用上道题的思路

同样这样匹配,我们计算在当前\(S_i\)的匹配中节点能匹配的最长长度

而每个节点在多个匹配中的答案显然是每次匹配的最小值

至于最长长度,从\(Link\)树上\(dfs\)即可

这是推特上一个卖屁股的福利姬,

她用卖屁股攒下来的钱,买的公寓烂尾了……

315要啥动静?315报道过一次开发商烂尾吗?#include<bits/stdc++.h>
const int MAXN=1e5+5;
using namespace std;
struct SAM_node{
	int Len;
	int Next[26];
	int Link;
};
struct SAM{
	SAM_node Tree[MAXN*2]; 
	int cnt_node;
	vector<int>g[MAXN*2];
	int Last;
    int mx[MAXN*2];
    int mi[MAXN*2];
    int Flag_Isbuild;
	void Build()
	{
		cnt_node=0;
		Last=0;
		Tree[0].Link=-1;
		Tree[0].Len=0;
		return;
	}
	void Insert(char s)
	{
        if(!Flag_Isbuild)
        {
            Flag_Isbuild=1;
            Build();
        }
		int p=Last;
		int cur=++cnt_node;
		p=Tree[Last].Link;
		Tree[Last].Next[s-'a']=cur;
		Tree[cur].Len=Tree[Last].Len+1;
		while((p!=-1)&&(!Tree[p].Next[s-'a']))
		{
			Tree[p].Next[s-'a']=cur;
			p=Tree[p].Link;
		}		
		if(p==-1)
		{
			Tree[cur].Link=0;
		}
		else
		{
			int q=Tree[p].Next[s-'a'];
			if(Tree[q].Len==Tree[p].Len+1)
			{
				Tree[cur].Link=q;
			}
			else
			{
				int clone=++cnt_node;
				Tree[clone]=Tree[q];
				Tree[clone].Len=Tree[p].Len+1;
				Tree[q].Link=clone;
				Tree[cur].Link=clone;
				while((p!=-1)&&(Tree[p].Next[s-'a']==q))
				{
					Tree[p].Next[s-'a']=clone;
					p=Tree[p].Link;
				}
			}
		}
		Last=cur;
	}
    void LinkB()
	{
		for(int i=0;i<=cnt_node;i++)
		{
			if(Tree[i].Link==-1)
			{
				continue;
			}
			g[Tree[i].Link].push_back(i);
		}
	}
    void dfs(int x)
    { 
        
        for(int i=0;i<g[x].size();i++)
        {
            int v=g[x][i];
            dfs(v);
            mx[x]=max(mx[x],mx[v]);
        }
        mx[x]=min(mx[x],Tree[x].Len);
        mi[x]=min(mi[x],mx[x]);
    }
}sam;
bool cmp(string x,string y)
{
    return x.size()<y.size();
}
string S[15];
int main()
{
  //freopen("date.in","r",stdin);
  // freopen("rnm.out","w",stdout);
    int Cnt;
    scanf("%d",&Cnt);
    for(int i=1;i<=Cnt;i++)
    {
        cin>>S[i];
    }
    sort(S+1,S+1+Cnt,cmp);
    for(int i=0;i<S[1].size();i++)
    {
        sam.Insert(S[1][i]);
    }
    sam.LinkB();
    for(int i=0;i<=sam.cnt_node;i++)
    {
        sam.mi[i]=0x3f3f3f3f;
    }
    for(int C=2;C<=Cnt;C++)
    {
        int p=0;
        int l=0;
        int Ans=0;
        for(int i=0;i<=sam.cnt_node;i++)
        {
             sam.mx[i]=0;
        }
        for(int i=0;i<S[C].size();i++)
        {
            if(sam.Tree[p].Next[S[C][i]-'a'])
            {
                p=sam.Tree[p].Next[S[C][i]-'a'];
                l++;
            }
            else
            {
                while((p!=-1)&&(!sam.Tree[p].Next[S[C][i]-'a']))
                {
                    p=sam.Tree[p].Link;
                    l=sam.Tree[p].Len;
                }
                if(p==-1)
                {
                    p=0;
                    l=0;
                }
                else
                {
                    p=sam.Tree[p].Next[S[C][i]-'a'];
                    l++;
                }

            }
            sam.mx[p]=max(l,sam.mx[p]);
        }

        sam.dfs(0);
    }
    int Ans=0;
    for(int i=sam.cnt_node;i>=1;i--)
    {
        Ans=max(Ans,sam.mi[i]);
    }
    printf("%d\n",Ans);
    
} 

一些例题

甲苯先生和大中锋的字符串

不用多说,字串出现次数明显可以维护,剩下的就是简单区间加

CF802I Fake News (hard)

和上道题差不多的

SP8093 JZPGYZ - Sevenk Love Oimaster

这是个广义后缀自动机

在这里先明确一下\(clone\)的含义

在做后缀自动机的时候我们在\(len_p+1=len_q\)时直接将\(Link_{cur}=q\),相当于\(cur\)\(Pos\)是可以直接添加进\(R\)中的

\(len_p+1<len_q\),说明有另一条路径可以抵达\(q\)且更长,而\(cur\)对应的\(Pos\)可以作为另一个右端点从\(q\)的字符串中分出一部分,所以要建立一个\(clone\),同时\(Pos\in R_{clone},\notin R_q\)

明确之后,对多串建立一个\(SAM\),一个串插完后直接令\(Last=0\),但要注意现在的\(Last\)可能有\(ch\)的连边

回到这道题,建完后直接启发式合并\(set\)即可

#include<bits/stdc++.h>
const int MAXN=1e5+5;
using namespace std;
struct SAM_node{
	int Len;
	int Next[26];
	int Link;
    set<int>Num;
};
int Ans[MAXN*2];
struct SAM{
	SAM_node Tree[MAXN*2]; 
	int cnt_node;
	vector<int>g[MAXN*2];
	int Last;
    int mx[MAXN*2];
    int mi[MAXN*2];
    int Flag_Isbuild;
    vector<int>query[MAXN*2];
     int New()
    {
        ++cnt_node;
        Tree[cnt_node].Link=0;
        for(int i=0;i<26;i++)
        {
            Tree[cnt_node].Next[i]=0;
        }
        Tree[cnt_node].Len=0;
        return cnt_node;
    }
	void Build()
	{
		cnt_node=0;
		Last=0;
        for(int i=0;i<26;i++)
        {
            Tree[cnt_node].Next[i]=0;
        }
		Tree[0].Link=-1;
		Tree[0].Len=0;
		return;
	}
	void Insert(char s,int Pos)
	{
        if(!Flag_Isbuild)
        {
            Flag_Isbuild=1;
            Build();
        }
        if(Tree[Last].Next[s-'a'])
        {
            int p=Last;
            int q=Tree[p].Next[s-'a'];
			if(Tree[q].Len==Tree[p].Len+1)
			{
				Tree[q].Num.insert(Pos);
                Last=q;
			}
			else
			{
				int clone=++cnt_node;
				 for(int i=0;i<26;i++)
                {
                    Tree[clone].Next[i]=Tree[q].Next[i];
                }
                Tree[clone].Num.clear();
				Tree[clone].Len=Tree[p].Len+1;
                Tree[clone].Link=Tree[q].Link;
				Tree[q].Link=clone;
				while((p!=-1)&&(Tree[p].Next[s-'a']==q))
				{
					Tree[p].Next[s-'a']=clone;
					p=Tree[p].Link;
				}
                Tree[clone].Num.insert(Pos);
                Last=clone;
			}
            return;
        }
		int p=Last;
		int cur=++cnt_node;
		p=Tree[Last].Link;
		Tree[Last].Next[s-'a']=cur;
		Tree[cur].Len=Tree[Last].Len+1;
		while((p!=-1)&&(!Tree[p].Next[s-'a']))
		{
			Tree[p].Next[s-'a']=cur;
			p=Tree[p].Link;
		}		
		if(p==-1)
		{
			Tree[cur].Link=0;
		}
		else
		{
			int q=Tree[p].Next[s-'a'];
			if(Tree[q].Len==Tree[p].Len+1)
			{
				Tree[cur].Link=q;
			}
			else
			{
				int clone=++cnt_node;
				 for(int i=0;i<26;i++)
                {
                    Tree[clone].Next[i]=Tree[q].Next[i];
                }
                Tree[clone].Link=Tree[q].Link;
                Tree[clone].Num.clear();
				Tree[clone].Len=Tree[p].Len+1;
				Tree[q].Link=clone;
				Tree[cur].Link=clone;
				while((p!=-1)&&(Tree[p].Next[s-'a']==q))
				{
					Tree[p].Next[s-'a']=clone;
					p=Tree[p].Link;
				}
			}
		}
        Tree[cur].Num.insert(Pos);
		Last=cur;
	}
    void LinkB()
	{
        for(int i=0;i<=cnt_node;i++)
        {
            g[i].clear();
        }
		for(int i=0;i<=cnt_node;i++)
		{
			if(Tree[i].Link==-1)
			{
				continue;
			}
			g[Tree[i].Link].push_back(i);
		}
	}
    void dfs(int x)
    { 
        for(int i=0;i<g[x].size();i++)
        {
            int v=g[x][i];
            dfs(v);
            if(Tree[v].Num.size()>Tree[x].Num.size())
            {
                swap(Tree[v].Num,Tree[x].Num);
            }
            for(set<int>::iterator it=Tree[v].Num.begin();it!=Tree[v].Num.end();it++)
            {
                Tree[x].Num.insert((*it));
            }
        }
        for(int i=0;i<query[x].size();i++)
        {
            Ans[query[x][i]]=Tree[x].Num.size();
        }
    }
    void Query(string S,int Pos)
    {
        int p=0;
        for(int i=0;i<S.size();i++)
        {
            if(Tree[p].Next[S[i]-'a'])
            {
                p=Tree[p].Next[S[i]-'a'];
            }
            else{
                return;
            }
        }
        query[p].push_back(Pos);
    }
}sam;
int T;
string S;
int n,m;

int main()
{
  ///freopen("date.in","r",stdin);
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        cin>>S;
        for(int j=0;j<S.size();j++)
        {
            sam.Insert(S[j],i);
        }
        sam.Last=0;
    }
    for(int i=1;i<=m;i++)
    {
        cin>>S;
        sam.Query(S,i);
    }
    sam.LinkB();
    sam.dfs(0);
    for(int i=1;i<=m;i++)
    {
        printf("%d\n",Ans[i]);
    }
} 

[BJOI2020] 封印

\(Lp_i\)\(S\)\(i\)结尾最长匹配长度

很明显答案为\(max(min(Lp_i,i-L+1))\),注意到\(Lp_i-i\)单减

[ZJOI2015]诸神眷顾的幻想乡

这里说说广义后缀自动机的另一种建法

我们认为\(NYH\)是神,所以就知道怎么建了

考虑把所有的字符串先插入\(Tire\)\(Bfs\)建后缀自动机

回到这到题,注意到叶子数很少,如果我们以每个叶子为根建\(Tire\)后插入后缀自动机,很明显是可以覆盖到所有子串的

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+5;
struct SAM_node{
	int Len;
	int Next[10];
	int Link;
};
struct SAM{
    SAM_node Tree[MAXN*2];
    int Last;
    int cnt_node;
    void Build()
    {
        cnt_node=0;
        Tree[0].Len=0;
        Tree[0].Link=-1;
        return;
    }
    int Insert(int p,int ch)
    {
        int cur=Tree[p].Next[ch];
        Tree[cur].Len=Tree[p].Len+1;
        p=Tree[p].Link;
        while((p!=-1)&&(!Tree[p].Next[ch]))
        {
            Tree[p].Next[ch]=cur;
            p=Tree[p].Link;
        }
        if(p==-1)
        {
            Tree[cur].Link=0;
            return cur;
        }
        else
        {
            int q=Tree[p].Next[ch];
            if(Tree[p].Len+1==Tree[q].Len)
            {
                Tree[cur].Link=q;
                return cur;
            }
            else
            {
                int clone=++cnt_node;
                for(int i=0;i<10;i++)
                {
                    if(Tree[Tree[q].Next[i]].Len)
                    {
                        Tree[clone].Next[i]=Tree[q].Next[i];
                    }
                    
                }
                Tree[clone].Len=Tree[p].Len+1;
                Tree[clone].Link=Tree[q].Link;
                Tree[q].Link=clone;
                Tree[cur].Link=clone;
                while((p!=-1)&&(Tree[p].Next[ch]==q))
                {
                    Tree[p].Next[ch]=clone;
                    p=Tree[p].Link;
                }
                return cur;
            }
        }
    }
    void Bfs()
    {
        queue<pair<int,int> >q;
        for(int i=0;i<10;i++)
        {
            if(Tree[0].Next[i])
            {
                q.push(make_pair(0,i));
            }
        }
        while(q.size())
        {
            pair<int,int>temp=q.front();
            q.pop(); 
            int Cur=Insert(temp.first,temp.second);
            for(int i=0;i<10;i++)
            {
                if(Tree[Cur].Next[i])
                {
                    q.push(make_pair(Cur,i));
                }
            }
        }
        return;
    }
}sam;
int n,C;
int rd[MAXN];
int Clor[MAXN];
vector<int>g[MAXN];
int Vis[MAXN];
int main()
{
   freopen("date.in","r",stdin);
   freopen("date.out","w",stdout);
    sam.Build();
    scanf("%d %d",&n,&C);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&Clor[i]);
    }
    for(int i=1;i<n;i++)
    {
        int x,y;
        scanf("%d %d",&x,&y);
        g[x].push_back(y);
        g[y].push_back(x);
        rd[x]++;
        rd[y]++;
    }
    for(int x=1;x<=n;x++)
    {
        if(rd[x]==1)
        {
            queue<pair<int,int> >q;
            memset(Vis,0,sizeof(Vis));
            q.push(make_pair(x,0));
            while(q.size())
            {
                pair<int,int>temp=q.front();
                q.pop();
                Vis[temp.first]=1;
                int Popp=temp.second;
                int Too=Clor[temp.first];
                if(!sam.Tree[Popp].Next[Too])
                {
                    sam.Tree[Popp].Next[Too]=++sam.cnt_node;
                }
                int Cur=sam.Tree[Popp].Next[Too];
                for(int i=0;i<g[temp.first].size();i++)
                {
                    int v=g[temp.first][i];
                    if(Vis[v])
                    {
                        continue;
                    }
                    q.push(make_pair(v,Cur));
                }
            }           
        }
    }
    sam.Bfs();
    long long Ans=0;
    for(int i=1;i<=sam.cnt_node;i++)
    {

        Ans+=(sam.Tree[i].Len-sam.Tree[sam.Tree[i].Link].Len);
    }
    printf("%lld\n",Ans);
}

「雅礼集训 2017 Day7」事情的相似度

首先不难想到如果确定\(l\le i<j\le r\),我们可以直接在\(SAM\)\(Link\)树上找\(i,j\)对应节点的\(LCA\)

照这个思路,我们可以考虑每个节点对一个询问的影响,考虑用\(bitset\)记录每个节点能覆盖的询问

注意这里我们要按\(Len\)从大到小合并,一个询问如果被在他的子树即本身覆盖两次及以上,那这个节点就是答案,为了保证一个询问只会访问到一次,我们要记录是否被访问

还有就是这个空间好像是\(1e9\),我们可以把\(m\)分成\(4\)部分计算

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+5;
const int Lit=(MAXN/4);
struct SAM_node{
    int link,Next[2],Len;
}Tree[MAXN*2];
int cnt_node;
int Last=0;
int n,m;
string s;
vector<int>g[MAXN*2];
int Rec[MAXN*2];
void Build()
{
    cnt_node=0;
    Last=0;
    Tree[0].link=-1;
    Tree[0].Len=0;
    for(int i=0;i<2;i++)
    {
        Tree[0].Next[i]=0;
    }
    return;
}
int Insert(int p,int sh)
{
    int cur=++cnt_node;
    Tree[cur].Len=Tree[p].Len+1;
    while((p!=-1)&&(!Tree[p].Next[sh]))
    {
        Tree[p].Next[sh]=cur;
        p=Tree[p].link;
    }
    if(p==-1)
    {
        Tree[cur].link=0;
    }
    else
    {
       int q=Tree[p].Next[sh];
        if(Tree[q].Len==Tree[p].Len+1)
        {
            Tree[cur].link=q;
        }
        else
        {
            int clone=++cnt_node;
            Tree[clone].link=Tree[q].link;
            Tree[q].link=clone;
            Tree[cur].link=clone;
            for(int i=0;i<2;i++)
            {
                Tree[clone].Next[i]=Tree[q].Next[i];
            }
            Tree[clone].Len=Tree[p].Len+1;
            while((p!=-1)&&(Tree[p].Next[sh]==q))
            {
                Tree[p].Next[sh]=clone;
                p=Tree[p].link;
            }
        }
    }
    
    return cur;
}
int l,r;
bitset<Lit>S[MAXN*2];
bitset<Lit>Used,New;
int Ans[MAXN*2];
int Rsort[MAXN*2];
struct Query{
    int l,r;
}query[MAXN];

void solve()
{
    if(!m)
    {
        return;
    }
    int Cnt=0;
    while(m)
    {
        m--;
        scanf("%d %d",&l,&r);
        query[Cnt].l=l;
        query[Cnt].r=r;
        Cnt++;
        if(Cnt==Lit)
        {
            break;
        }
    }
    for(int i=0;i<=cnt_node;i++)
    {
        S[i].reset();
    }
    

    for(int i=0;i<Cnt;i++)
    {
        Used.set(i);
        S[Rec[query[i].l]].set(i);
        S[Rec[query[i].r+1]].set(i);
    }
    for(int i=2;i<=n;i++)
    {
        S[Rec[i]]^=S[Rec[i-1]];
    }
    for(int ps=0;ps<=cnt_node;ps++)
    {
        int x=Rsort[ps];
        for(int i=0;i<g[x].size();i++)
        {
            int v=g[x][i];
            New=S[x]&S[v]&Used;
            S[x]|=S[v];
            
            for(int j=New._Find_first();j!=New.size();j=New._Find_next(j))
            {
                Ans[j]=Tree[x].Len;
                Used.reset(j);
            }
        }
    }
    for(int i=0;i<Cnt;i++)
    {
        printf("%d\n",Ans[i]);
    }
}
int cmp(int x,int y)
{
    return Tree[x].Len>Tree[y].Len;
}
int main()
{
    freopen("date.in","r",stdin);
    // freopen("date.out","w",stdout);
    Build();
    scanf("%d %d",&n,&m);
    cin>>s;
    for(int i=0;i<s.size();i++)
    {
        Last=Insert(Last,s[i]-'0');
        Rec[i+1]=Last;
    }
    for(int i=0;i<=cnt_node;i++)
    {
        if(Tree[i].link==-1)
        {
            continue;
        }
        g[Tree[i].link].push_back(i);
        Rsort[i]=i;
    }

    sort(Rsort,Rsort+cnt_node+1,cmp);
    solve();
    solve();
    solve();
    solve(); 
}
posted @ 2023-03-23 22:29  kid_magic  阅读(26)  评论(0编辑  收藏  举报