嘘~ 正在从服务器偷取页面 . . .

OI模板-字符串


OI模板-字符串

字符串哈希

单哈希

给定 \(N\) 个字符串(第 \(i\) 个字符串长度为 \(M_i\),字符串内包含数字、大小写字母,大小写敏感),请求出 \(N\) 个字符串中共有多少个不同的字符串。

P3370 【模板】字符串哈希

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <iomanip>
#include <random>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
#define N (int)(1e4+15)
#define M (int)(1515)
const int hash_base=31;
const int hash_mod=998244353;
char str[M];
int n,cnt=1,t[N];
struct hash_str
{
    int len,hsh[M],pwd[M];
    void Hash(int l,char *str)
    {
        len=l; pwd[0]=1; hsh[0]=0;
        for(int i=1; i<=l; i++)
        {
            pwd[i]=pwd[i-1]*hash_base%hash_mod;
            hsh[i]=(hsh[i-1]*hash_base%hash_mod+str[i])%hash_mod;
        }
    }
    int gethash(int l,int r)
    {
        int res=(hsh[r]-hsh[l-1]*pwd[r-l+1])%hash_mod;
        res=(res+hash_mod)%hash_mod;
        return res;
    }
}p;
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    cin >> n;
    for(int i=1; i<=n; i++)
    {
        cin >> (str+1);
        p.Hash(strlen(str+1),str);
        t[i]=p.gethash(1,p.len);
    }
    sort(t+1,t+1+n);
    for(int i=2; i<=n; i++)
        if(t[i]!=t[i-1])++cnt;
    cout << cnt << '\n';
    return 0;
}

双哈希

详见CF1200E Compress Words 题解

给定一堆字符串,依次插入答案串尾部,每次删掉答案串的后缀 与 待插入串的前缀的最大匹配串

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
#define N (int)(1e6+5)
#define hash_cnt 2
const int hash_base[hash_cnt]={29,31};
const int hash_mod[hash_cnt]={1000000007,998244353};
struct hash_str
{
    char s[N];
    int len,hsh[hash_cnt][N];
    int pwd[hash_cnt][N];
    
    void init()
    {
        len=0;
        for(int i=0; i<hash_cnt; i++)
        {
            hsh[i][0]=0;
            pwd[i][0]=1;
        }
    }
    hash_str(){init();}
    void add(char ch)
    {
        s[++len]=ch;
        for(int i=0; i<hash_cnt; i++)
        {
            pwd[i][len]=pwd[i][len-1]*hash_base[i]%hash_mod[i];
            hsh[i][len]=(hsh[i][len-1]*hash_base[i]+ch)%hash_mod[i];
        }
    }
    vector<int> gethash(int l,int r)
    {
        vector<int> res(hash_cnt,0);
        for(int i=0; i<hash_cnt; i++)
        {
            int t=(hsh[i][r]-hsh[i][l-1]*pwd[i][r-l+1])%hash_mod[i];
            t=(t+hash_mod[i])%hash_mod[i];
            res[i]=t;
        }
        return res;
    }
}s,t;
bool equal(const vector<int> &vec1,const vector<int> &vec2)
{
    assert(vec1.size()==vec2.size());
    for(int i=0; i<vec1.size(); i++)
        if(vec1[i]!=vec2[i])return 0;
    return 1;
}
int n;
char str[N];
void solve(char *str)
{
    int len=strlen(str+1);
    t.init();
    for(int i=1; i<=len; i++)
        t.add(str[i]);
    int d=1;
    for(int j=min(len,s.len); j>=1; j--)
        if(equal(t.gethash(1,j),s.gethash(s.len-j+1,s.len)))
            {d=j+1;break;}
    for(int i=d; i<=len; i++)
        s.add(str[i]);
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    cin >> n;
    for(int i=1; i<=n; i++)
    {
        cin >> str+1;
        solve(str);
    }
    cout << s.s+1 << endl;
    return 0;
}

KMP

时间复杂度 \(O(n+m)\)

空间复杂度 \(O(n+m)\)

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define MAXN (int)(1e6+5)
int n,m,fail[MAXN];
char t[MAXN],s[MAXN];
signed main()
{
	scanf("%s\n%s\n",t+1,s+1);
	n=strlen(t+1);m=strlen(s+1);
	for(int i=2,j=0; i<=m; i++)
	{
		while(j&&s[i]!=s[j+1])j=fail[j];
		if(s[i]==s[j+1])++j;
		fail[i]=j;
	}
	for(int i=1,j=0; i<=n; i++)
	{
		while(j&&t[i]!=s[j+1])j=fail[j];
		if(t[i]==s[j+1])++j;
		if(j==m)printf("%lld\n",i-m+1);
	}
	for(int i=1; i<=m; i++)
		printf("%lld%c",fail[i]," \n"[i==m]);
	return 0;
}

ExKMP(Z 函数)

时间复杂度 \(O(n+m)\)

空间复杂度 \(O(n+m)\)

b 与 b的后缀 的最大前缀、b 与 a的后缀 的最大前缀

注意该代码多测需要清空!

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
#define N (int)(2e7+5)
char a[N],b[N];
int z[N],p[N],n,m;
int ans1,ans2;
void exkmp()
{
    z[1]=m;
    for(int i=2,l=0,r=0; i<=m; i++)
    {
        if(i<=r)z[i]=min(r-i+1,z[i-l+1]);
        while(i+z[i]<=m&&b[i+z[i]]==b[z[i]+1])++z[i];
        if(i+z[i]-1>=r)l=i,r=i+z[i]-1;
    }
    for(int i=1,l=0,r=0; i<=n; i++)
    {
        if(i<=r)p[i]=min(r-i+1,z[i-l+1]);
        while(i+p[i]<=n&&a[i+p[i]]==b[p[i]+1])++p[i];
        if(i+p[i]-1>=r)l=i,r=i+p[i]-1;
    }
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    cin >> a+1 >> b+1;
    n=strlen(a+1);m=strlen(b+1);
    exkmp();
    int res=0;
    for(int i=1; i<=m; i++)res^=i*(z[i]+1);
    cout << res << endl;
    res=0;
    for(int i=1; i<=n; i++)res^=i*(p[i]+1);
    cout << res << endl;
    return 0;
}

Manacher

时间复杂度 \(O(n)\)

空间复杂度 \(O(n)\)

内存105.53MB(比较极限的数据)

aabaa
#a#a#b#a#a#

# a # a # b # a # a #
1 2 3 2 1 6 1 2 3 2 1
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <iomanip>
using namespace std;
// #define int long long
// #define INF 0x3f3f3f3f3f3f3f3f
#define N (int)(1.1e7+15)

char s[N<<1];
int n,p[N<<1];
void Manacher(int l)
{
    int r=1,mid=1,ans=0;
    for(int i=1; i<=l; i++)
    {
        p[i]=(i<=r)?min(p[(mid<<1)-i],r-i+1):1;
        while(s[i-p[i]]==s[i+p[i]]) ++p[i];
        if(i+p[i]-1>r)r=i+p[i]-1,mid=i;
        // 这里如果写 i+p[i]-1>=r 则会在r相等时优先更新mid
        // 对p数组的求解没有影响,但是在某些题目中会出现问题
        ans=max(ans,p[i]-1);
    }
    cout << ans << '\n';
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    cin >> (s+1); n=strlen(s+1);
    for(int i=n; i; i--)
        s[i*2]=s[i],s[i*2+1]='#';
    s[0]='$';s[1]='#';
    Manacher(n*2+1);
    return 0;
}

Trie

这个是老版本的(The XOR Largest Pair),太简单了不写一遍了,详见AC自动机即可

时间复杂度 \(O(\max{\{|s_i|\}})\)

空间复杂度 \(O(k\sum |s_i|),k\) 为字符集范围

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define MAXN (int)(1e5+5)
#define INF 0x3f3f3f3f3f3f3f3f
template<typename T>inline void read(T &k)
{
	char ch=getchar();T x=0,f=1;
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	k=x*f;
}
template<typename T>void write(T k)
{
	if(k<0){putchar('-');k=-k;}
	if(k>9)write(k/10);
	putchar(k%10+'0');
}
int n,ans=0;
int a[MAXN];
struct Trie
{
	int trie[MAXN*32][3],tot=1; // trie的大小要注意
	void insert(int x)
	{
		int p=0;
		for(int i=31; i>=0; --i) // 其实只要30就可以了
		{
			int k=(x>>i)&1;
			if(!trie[p][k])trie[p][k]=++tot;
			p=trie[p][k];
		}
	}
	int qry(int x)
	{
		int ans=0,p=0;
		for(int i=31; i>=0; --i)
		{
			int k=(x>>i)&1;
			if(trie[p][k^1])p=trie[p][k^1],ans|=(1<<i); // 尽可能选择这一位相反的
			else p=trie[p][k];
		}
		return ans;
	}
}t[1]; // 这个[1]并没有什么用 qwq
signed main()
{
	read(n);
	for(int i=1; i<=n; i++)
	{
		read(a[i]);
		t[0].insert(a[i]);
		ans=max(ans,t[0].qry(a[i]));
	}
	write(ans);putchar('\n');
	return 0;
}

01Trie

封装版

int n,a[N],res[N];
struct Trie
{
    int tot,trie[L][2],ed[L];
    void clear()
    {
        tot=0;
        memset(trie,0,sizeof(trie));
        memset(ed,0,sizeof(ed));
    }
    void insert(int i)
    {
        int u=0, x=a[i];
        for(int j=30,c; j>=0; j--)
        {
            c = (x >> j) & 1;
            if(!trie[u][c]) trie[u][c]=++tot;
            u=trie[u][c];
        }
        ed[u]=i;
    }
    // find max{x xor a[j]} (j ≠ i)
    int query(int x)
    {
        int u=0;
        for(int j=30,c; j>=0; j--)
        {
            c = (x >> j) & 1;
            if(trie[u][c^1]) u=trie[u][c^1];
            else if(trie[u][c]) u=trie[u][c];            
        }
        return a[ed[u]];
    }
}tr;

AC自动机

这个 AC 自动机的模板题在洛谷搞了3个属实离谱

简单来说,第一个就是建自动机+暴力跑 fail 树算答案,第三个就是建自动机+高级的方式算答案。

建议直接看 AC 自动机(二次加强版),以及后面的高级写法

AC自动机(简单版)

时间复杂度 \(O\left(\sum|s_i|+n|\Sigma| + |S|\right)\)\(n \le \sum|s_i|\) 表示节点数

如果不连 trie 图,并且在构建 fail 指针的时候避免遍历到空儿子,时间复杂度就是 \(\mathcal{O}(\sum|s_i| + |S|)\)

空间复杂度 \(O(|\Sigma|\cdot\sum |s_i| + |S|)\)

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define MAXN (int)(1e6+5)
int n;
queue<int>q;
char tmp[MAXN],t[MAXN];
int trie[MAXN][28],e[MAXN],tot,fail[MAXN];
void insert(int l,char *s)
{
	int p=0;
	for(int i=1; i<=l; i++)
	{
		int c=s[i]-'a';
		if(!trie[p][c])trie[p][c]=++tot;
		p=trie[p][c];
	}
	++e[p];
}
void build()
{
	for(int i=0; i<26; i++)
		if(trie[0][i])q.push(trie[0][i]);
	while(!q.empty())
	{
		int u=q.front();q.pop();
		for(int i=0; i<26; i++)
		{
			if(trie[u][i])
			{
				fail[trie[u][i]]=trie[fail[u]][i];
				q.push(trie[u][i]);
			}else trie[u][i]=trie[fail[u]][i];
		}
	}
}
int query(int l,char *t)
{
	int res=0,u=0;
	for(int i=1; i<=l; i++)
	{
		u=trie[u][t[i]-'a'];
		for(int j=u; j&&e[j]!=-1; j=fail[j])
			res+=e[j],e[j]=-1;
	}
	return res;
}
signed main()
{
	scanf("%lld",&n);
	for(int i=1; i<=n; i++)
	{
		scanf("%s",tmp+1);
		insert(strlen(tmp+1),tmp);
	}
	scanf("%s",t+1);
	build();
	printf("%lld\n",query(strlen(t+1),t));
	return 0;
}

AC自动机(加强版)

最坏时间复杂度 \(O(T|S|\max\{|s_i|\})\)

空间复杂度 \(O(|\Sigma|\cdot\sum |s_i| + |S|)\)

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define N (int)(155)
#define S (int)(75)
#define SZ (int)(155*75)
#define L (int)(1e6+5)
char t[L],s[N][S];
int n,cnt[N],e[SZ],val[SZ];
int trie[SZ][32],tot,fail[SZ];
void init()
{
	memset(fail,0,sizeof(fail));
	memset(cnt,0,sizeof(cnt));
	memset(e,0,sizeof(e));
	memset(trie,0,sizeof(trie));
	memset(val,0,sizeof(val));
	tot=0;
}
void insert(int l,char *s,int id)
{
	int u=0;
	for(int i=1; i<=l; i++)
	{
		int c=s[i]-'a';
		if(!trie[u][c])trie[u][c]=++tot;
		u=trie[u][c];
	}
	e[u]=id;
}
queue<int>q;
void build()
{
	for(int i=0; i<26; i++)
		if(trie[0][i])q.push(trie[0][i]);
	while(!q.empty())
	{
		int u=q.front();q.pop();
		for(int i=0; i<26; i++)
		{
			if(trie[u][i])
			{
				fail[trie[u][i]]=trie[fail[u]][i];
				q.push(trie[u][i]);
			}else trie[u][i]=trie[fail[u]][i];
		}
	}
}
int AC(int l,char *t)
{
	int u=0,res=0;
	for(int i=1; i<=l; i++)
	{
		u=trie[u][t[i]-'a'];
		for(int j=u; j; j=fail[j])
			++val[j];
	}
	for(int i=1; i<=tot; i++) if(e[i])
	{
		res=max(res,val[i]);
		cnt[e[i]]=val[i];
	}
	return res;
}
signed main()
{
	while(scanf("%lld",&n)&&n)
	{
		init();
		for(int i=1; i<=n; i++)
		{
			scanf("%s\n",s[i]+1);
			insert(strlen(s[i]+1),s[i],i);
		}
		scanf("%s\n",t+1);
		build();
		int mx=AC(strlen(t+1),t);
		printf("%lld\n",mx);
		for(int i=1; i<=n; i++)
			if(cnt[i]==mx)printf("%s\n",s[i]+1);
	}
	return 0;
}

AC自动机(二次加强版)

时间复杂度 \(O\left(\sum|s_i|+n|\Sigma| + |S|\right)\)\(n \le \sum|s_i|\) 表示节点数

如果不连 trie 图,并且在构建 fail 指针的时候避免遍历到空儿子,时间复杂度就是 \(\mathcal{O}(\sum|s_i| + |S|)\)

空间复杂度 \(O(|\Sigma|\cdot\sum |s_i| + |S|)\)

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define N (int)(2e5+5)
#define L (int)(2e6+5)
char t[L],s[N];
int n,ans[N],e[N],val[N],in[N];
int trie[N][32],tot,fail[N],f[N];
void init(){for(int i=1; i<=n; i++)f[i]=i;}
int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
void merge(int u,int v){f[find(u)]=find(v);}
void insert(int l,char *s,int id)
{
	int u=0;
	for(int i=1; i<=l; i++)
	{
		int c=s[i]-'a';
		if(!trie[u][c])trie[u][c]=++tot;
		u=trie[u][c];
	}
	if(!e[u])e[u]=id;
	else merge(id,e[u]);
}
queue<int>q;
void build()
{
	for(int i=0; i<26; i++)
		if(trie[0][i])q.push(trie[0][i]);
	while(!q.empty())
	{
		int u=q.front();q.pop();
		for(int i=0; i<26; i++)
		{
			if(trie[u][i])
			{
				fail[trie[u][i]]=trie[fail[u]][i];
				++in[trie[fail[u]][i]];
				q.push(trie[u][i]);
			}else trie[u][i]=trie[fail[u]][i];
		}
	}
}
void AC(int l,char *t)
{
	int u=0;
	for(int i=1; i<=l; i++)
	{
		u=trie[u][t[i]-'a'];
		++val[u];
	}
	for(int i=1; i<=tot; i++)
		if(!in[i])q.push(i);
	while(!q.empty())
	{
		int u=q.front();q.pop();
		if(e[u])ans[e[u]]=val[u];
		val[fail[u]]+=val[u];
		if(!--in[fail[u]])q.push(fail[u]);
	}
}
signed main()
{
	scanf("%lld",&n); init();
	for(int i=1; i<=n; i++)
	{
		scanf("%s\n",s+1);
		insert(strlen(s+1),s,i);
	}
	scanf("%s\n",t+1);
	build();
	AC(strlen(t+1),t);
	for(int i=1; i<=n; i++)
		printf("%lld\n",ans[find(i)]);
	return 0;
}

线性构建AC自动机

这是做 P9089 时,在题解区看到的高级写法

众所周知,我们一般的建图都是建的“字典图”,而这种方式是直接建 字典树+fail树

直接搬原题代码了

时间复杂度 \(\mathcal{O}(\sum |s_i|)\)


#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
typedef pair<int,int> pii;
void up(int &x,int y) { x < y ? x = y : 0; }
void down(int &x,int y) { x > y ? x = y : 0; }
#define N ((int)(1e6 + 15))

int n,f[N],g[N],dep[N]; string s[N];
int pos = 1,tot,fa[N],head[N],fail[N],tr[N][26];
struct Edge { int u,v,c,next; } e[N];
void addEdge(int u,int v,int c)
{
    tr[u][c] = v; fa[v] = u; dep[v] = dep[u] + 1;
    e[++pos] = {u,v,c,head[u]}; head[u] = pos;
}
void insert(string s)
{
    int u = 0, l = s.size();
    for(int i = 0; i < l; i++)
    {
        int c = s[i] - 'a';
        if(!tr[u][c]) addEdge(u, ++tot, c);
        u = tr[u][c];
    }
}
void build()
{
    queue<pii> q;
    for(int i = 0; i < 26; i++) if(tr[0][i]) q.push({tr[0][i], i});
    while(!q.empty())
    {
        int u,c,x; tie(u,c) = q.front(); q.pop();
        for(x = fail[fa[u]]; x && !tr[x][c]; x = fail[x]);
        if(x != fa[u]) fail[u] = tr[x][c];
        g[u] = fail[u] ? g[fail[u]] : dep[u];
        for(int i = head[u]; i; i = e[i].next) q.push({e[i].v, e[i].c});
    }
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    cin >> n;
    for(int i = 1; i <= n; i++)
    {
        cin >> s[i];
        reverse(s[i].begin(), s[i].end()); insert(s[i]);
    }
    build(); int res = 0;
    for(int o = 1; o <= n; o++)
    {
        int m = s[o].size(), u = 0; f[0] = 0;
        for(int i = 1; i <= m; i++)
        {
            u = tr[u][s[o][i - 1] - 'a'];
            f[i] = f[i - g[u]] + 1; res += f[i];
        }
    }
    cout << res << '\n';
    return 0;
}


后缀数组 SA

计数排序实现是 \(O(n\log n)\) ,直接 sort 是 \(O(n\log^2 n)\)

实测 \(n \le 10^6\) 时直接 sort 稍微慢一些,不过相对容易理解。

sa[i] 表示将排名第 \(i\) 的后缀(排名:所有后缀排序后第 \(i\) 小)

rk[i] 表示后缀 \(i\) 的排名,满足 sa[rk[i]] = rk[sa[i]] = i

计数排序版本:

// 2024年06月05日 15:04:10
#include <bits/stdc++.h>
using namespace std;
// #define int long long
// #define INF 0x3f3f3f3f3f3f3f3f
void up(int &x, int y) { x < y ? x = y : 0; }
void down(int &x, int y) { x > y ? x = y : 0; }
#define N ((int)(1e6 + 15))
#define check(i, w) (tmp[sa[i]] == tmp[sa[i - 1]] && tmp[sa[i] + w] == tmp[sa[i - 1] + w])

char s[N];
int n, m, sa[N], rk[N * 2], tmp[N * 2], cnt[N];
void sort(int w)
{
    memset(cnt, 0, sizeof(int) * (m + 1));
    for(int i = 1; i <= n; i++) tmp[i] = sa[i];
    for(int i = 1; i <= n; i++) ++cnt[rk[tmp[i] + w]];
    for(int i = 1; i <= m; i++) cnt[i] += cnt[i - 1];
    for(int i = n; i; i--) sa[cnt[rk[tmp[i] + w]]--] = tmp[i];
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    cin >> (s + 1); n = strlen(s + 1); m = max(n, 333); // m 是 rk 的值域
    for(int i = 1; i <= n; i++) { sa[i] = i, rk[i] = s[i]; }
    for(int w = 1; w < n; w *= 2)
    {
        sort(w); sort(0);
        for(int i = 1; i <= n; i++) tmp[i] = rk[i];
        for(int i = 1, p = 0; i <= n; i++)
            if(check(i, w)) rk[sa[i]] = p; else rk[sa[i]] = ++p;
    }
    for(int i = 1; i <= n; i++) cout << sa[i] << " \n"[i == n]; 
    return 0;
}

sort 版本:

#include <bits/stdc++.h>
using namespace std;
// #define int long long
// #define INF 0x3f3f3f3f3f3f3f3f
void up(int &x, int y) { x < y ? x = y : 0; }
void down(int &x, int y) { x > y ? x = y : 0; }
#define N ((int)(1e6 + 15))
#define check(i, w) (tmp[sa[i]] == tmp[sa[i - 1]] && tmp[sa[i] + w] == tmp[sa[i - 1] + w])

char s[N];
int n, w, sa[N], rk[N * 2], tmp[N * 2];
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    cin >> (s + 1); n = strlen(s + 1);
    for(int i = 1; i <= n; i++) { sa[i] = i, rk[i] = s[i]; }
    for(w = 1; w < n; w *= 2)
    {
        sort(sa + 1, sa + 1 + n, [](int x, int y) {
            return rk[x] == rk[y] ? rk[x + w] < rk[y + w] : rk[x] < rk[y];
        });
        for(int i = 1; i <= n; i++) tmp[i] = rk[i];
        for(int i = 1, p = 0; i <= n; i++)
            if(check(i, w)) rk[sa[i]] = p; else rk[sa[i]] = ++p;
    }
    for(int i = 1; i <= n; i++) cout << sa[i] << " \n"[i == n]; 
    return 0;
}

后缀自动机 SAM

时间复杂度 \(O(n)\)

空间复杂度 \(O(n)\)

// 2024年06月13日 22:03:17
#include <bits/stdc++.h>
using namespace std;
// #define int long long
// #define INF 0x3f3f3f3f3f3f3f3f
typedef long long ll;
void up(ll &x, ll y) { x < y ? x = y : 0; }
void down(int &x, int y) { x > y ? x = y : 0; }
#define N ((int)(2e6 + 15))

ll res; char s[N];
int tot, lst, dep[N]; vector<int> vec[N];
struct SAMnode { int ch[26], len, fa; } t[N];
void insert(const int c)
{
    int p = lst, now = lst = ++tot;
    t[now].len = t[p].len + 1; dep[now] = 1; 
    while(p > 0 && !t[p].ch[c]) { t[p].ch[c] = now; p = t[p].fa; }
    if(!p) { t[now].fa = 1; return; }
    int son = t[p].ch[c];
    if(t[son].len == t[p].len + 1) t[now].fa = son;
    else
    {
        t[++tot] = t[son]; t[tot].len = t[p].len + 1;
        t[son].fa = t[now].fa = tot;
        while(p > 0 && t[p].ch[c] == son) { t[p].ch[c] = tot; p = t[p].fa; }
    }
}
void dfs(int u)
{
    for(int v : vec[u]) { dfs(v), dep[u] += dep[v]; }
    if(dep[u] != 1) up(res, (ll)dep[u] * t[u].len);
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    cin >> (s + 1); tot = lst = 1;
    for(int i = 1; s[i]; i++) insert(s[i] - 'a');
    for(int i = 2; i <= tot; i++) vec[t[i].fa].push_back(i);
    dfs(1); cout << res << '\n';
    return 0;
}

广义后缀自动机 ExSAM

之前看到巨佬的一个hack,还不确定我这个对不对

这里是讨论 link

时间复杂度 \(O(n|\Sigma|)\)

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
#define N (int)(2e6+15)
#define M (int)(1e6+15)
struct trie_node
{
    signed ch[26],fa,val;
}tr[M];
struct SAM_node
{
    signed ch[26],len,fa;
}sam[N];
string s;
int n,cnt=1,tot=1,lst=1,pos[N];
void insert(string s)
{
    int p=1;
    for(int i=0; i<s.size(); i++)
    {
        int c=s[i]-'a';
        if(!tr[p].ch[c])
        {
            tr[p].ch[c]=++cnt;
            tr[cnt].fa=p;
            tr[cnt].val=c;
        }
        p=tr[p].ch[c];
    }
}
int update(int c,int lst)
{
    int p=lst,now=lst=++tot;
    sam[now].len=sam[p].len+1;
    for(; p&&!sam[p].ch[c]; p=sam[p].fa)
        sam[p].ch[c]=now;
    if(!p)sam[now].fa=1;
    else
    {
        int q=sam[p].ch[c];
        if(sam[q].len==sam[p].len+1)
            sam[now].fa=q;
        else
        {
            int clone=++tot;
            sam[clone]=sam[q];
            sam[clone].len=sam[p].len+1;
            sam[q].fa=sam[now].fa=clone;
            for(; p&&sam[p].ch[c]==q; p=sam[p].fa)
                sam[p].ch[c]=clone;
        }
    }
    return now;
}
queue<int> q;
void build()
{
    for(int i=0; i<26; i++)
        if(tr[1].ch[i])q.push(tr[1].ch[i]);
    pos[1]=1;
    while(!q.empty())
    {
        int u=q.front();q.pop();
        pos[u]=update(tr[u].val,pos[tr[u].fa]);
        for(int i=0; i<26; i++)
            if(tr[u].ch[i])q.push(tr[u].ch[i]);
    }
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    cin >> n;
    for(int i=1; i<=n; i++) 
        cin >> s,insert(s);
    build();
    int res=0;
    for(int i=2; i<=tot; i++)
        res+=sam[i].len-sam[sam[i].fa].len;
    cout << res << endl;
    return 0;
}

回文自动机 PAM

时间复杂度 \(O(n)\)

参考 洛谷P5496 【模板】回文自动机(PAM) 题解

// 2024年05月31日 20:03:37
#include <bits/stdc++.h>
using namespace std;
// #define int long long
// #define INF 0x3f3f3f3f3f3f3f3f
void up(int &x,int y) { x < y ? x = y : 0; }
void down(int &x,int y) { x > y ? x = y : 0; }
#define N ((int)(5e5 + 15))

char s[N];
int ans, tot, fail[N], len[N], cnt[N], ch[N][26];
int getfail(int p, int i)
{
    while(s[i - len[p] - 1] != s[i]) p = fail[p];
    return p;
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    cin >> (s + 1); fail[tot = 1] = 0; len[0] = -1;
    for(int i = 1, p, c; s[i]; i++)
    {
        if(i > 1) s[i] = (s[i] + ans - 97) % 26 + 97;
        c = s[i] - 'a'; p = i > 1 ? getfail(p, i) : 0;
        if(!ch[p][c])
        {
            ch[p][c] = ++tot; len[tot] = len[p] + 2; 
            fail[tot] = p > 0 ? ch[getfail(fail[p], i)][c] : 1;
            cnt[tot] = cnt[fail[tot]] + 1; 
        }
        p = ch[p][c]; ans = cnt[p]; cout << ans << ' ';
    }
    return 0;
}

子序列自动机

参考 洛谷P5826 【模板】子序列自动机 题解

朴素构建

过不了模板题,但是在一些题目里可以很方便写出来。

时间复杂度 \(\mathcal{O}(n |\Sigma|)\)

for(int i = len - 1; ~i; i--) {
    for(int j = 0; j < 26; j++) ch[i][j] = ch[i + 1][j];
    ch[i][s[i + 1] - 'a'] = i + 1;
}

可持久化线段树构建

时间复杂度 \(\mathcal{O}(n \log |\Sigma|+ \sum l_i \log |\Sigma|)\) ,其中 \(\sum l_i\) 表示询问序列的总长度。

// 2024年06月08日 14:10:02
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
void up(int &x, int y) { x < y ? x = y : 0; }
void down(int &x, int y) { x > y ? x = y : 0; }
#define N ((int)(1e5 + 15))

int a[N], b[N];
struct Tree
{
    Tree *ls, *rs; int l, r, v;
    // build
    Tree(int _l, int _r) : l(_l), r(_r), v(-1)
    {
        if(l != r)
        {
            int mid = (l + r) >> 1;
            ls = new Tree(l, mid); rs = new Tree(mid + 1, r);
        }
    }
    // Modify
    Tree(Tree *pre, int x, int _v) : l(pre -> l), r(pre -> r), v(0)
    {
        if(l == r) v = _v;
        else
        {
            if(x <= pre -> ls -> r) { 
                rs = pre -> rs; ls = new Tree(pre -> ls, x, _v);
            }else {
                ls = pre -> ls; rs = new Tree(pre -> rs, x, _v);
            }
        }
    }
    int query(int x)
    {
        if(l == r) return v;
        if(x <= ls -> r) return ls -> query(x);
        else return rs -> query(x);
    }
} *rt[N];
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    int _, n, q, m; cin >> _ >> n >> q >> m;
    for(int i = 1; i <= n; i++) cin >> a[i];
    rt[n] = new Tree(1, m);
    for(int i = n; i; i--) rt[i - 1] = new Tree(rt[i], a[i], i);
    for(int len, pos; q--; )
    {
        pos = 0; cin >> len;
        for(int i = 1; i <= len; i++) cin >> b[i];
        for(int i = 1; i <= len; i++)
        {
            pos = rt[pos] -> query(b[i]);
            if(pos == -1) break;
        }
        cout << ((~pos) ? "Yes" : "No") << '\n';
    }
    return 0;
}

vector + 二分

实际上模板题可以直接二分

时间复杂度 \(\mathcal{O}(n+ \sum l_i \log |\Sigma|)\) ,其中 \(\sum l_i\) 表示询问序列的总长度。

// 2024年06月08日 14:18:17
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
void up(int &x, int y) { x < y ? x = y : 0; }
void down(int &x, int y) { x > y ? x = y : 0; }
#define upper(a, b) upper_bound(a.begin(), a.end(), b)
#define N ((int)(1e5 + 15))

int a[N], b[N];
vector<int> pos[N];
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    int _, n, q; cin >> _ >> n >> q >> _;
    for(int i = 1; i <= n; i++) { cin >> a[i], pos[a[i]].push_back(i); }
    for(int p = 0, len; q--; )
    {
        cin >> len; p = 0; bool ok = true;
        for(int i = 1; i <= len; i++) cin >> b[i];
        for(int i = 1; i <= len; i++)
        {
            auto nxt = upper_bound(pos[b[i]].begin(), pos[b[i]].end(), p);
            if(nxt == pos[b[i]].end()) { ok = false; break; } else p = *nxt;
        }
        cout << (ok ? "Yes" : "No") << '\n';
    }
    return 0;
}

文章作者: q779
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 q779 !
评论
  目录