后缀自动机(SAM)构造实现过程演示+习题集锦

蒟蒻写这篇 b l o g blog blog主要是存一下,后缀自动机的详细搭建过程,方便以后复习
具体的某些证明,为什么这么做,正确性劈里啪啦一大堆就不赘述了讲解指路☞

后缀自动机

后缀自动机上每一条到 i i i的路径对应一个子串,整个自动机包含了字符串的所有子串

很多时候可以和后缀数组等价使用


e n d p o s endpos endpos:一个子串 i i i在整个字符串中出现的位置 最后一个字符的下标 构成的集合
举个栗子 a b c b c d e a b c abcbcdeabc abcbcdeabc,(从 0 0 0开始标号)
子串 a b c abc abc对应的 e n d p o s endpos endpos { 2 , 9 } \{2,9\} { 2,9},子串 b c bc bc e n d p o s endpos endpos { 2 , 4 , 9 } \{2,4,9\} { 2,4,9}

后缀自动机的编号对应的就是 e n d p o s endpos endpos完全相同的所有子串
依旧是上面的粒子 a b c b c d e a b c abcbcdeabc abcbcdeabc
子串 b c bc bc e n d p o s endpos endpos { 2 , 4 , 9 } \{2,4,9\} { 2,4,9},子串 c c c e n d p o s endpos endpos也为 { 2 , 4 , 9 } \{2,4,9\} { 2,4,9}
那么后缀自动机上对应的 i d id id编号既表示 b c bc bc子串,也表示 c c c子串

算法实现过程


  • e . g . 1 e.g.1 e.g.1,构建 a b c d abcd abcd的后缀自动机
    Ⅰ最初始状态,仅有一个空根, l a s t = 1 last=1 last=1 l a s t last last表示后缀自动机的最后一个节点
    在这里插入图片描述
    Ⅱ 将 ′ a ′ 'a' a扔进去,新建一个节点 c n t = 2 cnt=2 cnt=2 l e n = l e n [ l a s t ] + 1 = 1 len=len[last]+1=1 len=len[last]+1=1
    l a s t last last开始跳,发现 1 1 1没有 ′ a ′ 'a' a
    则建立一条 ′ a ′ 'a' a边,并指向新点 2 2 2
    此时跳到了初始源点, 2 2 2的后缀链接只能指向 1 1 1 l a s t last last变为 2 2 2
    在这里插入图片描述
    Ⅲ 将 ′ b ′ 'b' b扔进去,新建一个节点 c n t = 3 , l e n = l e n [ l a s t ] + 1 = 2 cnt=3,len=len[last]+1=2 cnt=3,len=len[last]+1=2
    l a s t last last开始跳后缀链接
    2 2 2没有 ′ b ′ 'b' b边,新建一条并指向 3 3 3,跳后缀链接到 1 1 1
    1 1 1没有 ′ b ′ 'b' b边,新建一条并指向 3 3 3
    此时已经到了根节点, 3 3 3的后缀链接只能指向 1 1 1 l a s t = 3 last=3 last=3
    在这里插入图片描述
    Ⅳ 将 ′ c ′ 'c' c扔进去,新建一个节点 c n t = 4 , l e n = 3 cnt=4,len=3 cnt=4,len=3
    l a s t last last开始跳后缀链接
    3 3 3没有 ′ c ′ 'c' c边,新建一条并指向 4 4 4,跳后缀链接到 1 1 1
    1 1 1没有 ′ c ′ 'c' c边,新建一条并指向 4 4 4
    此时已经到了根节点, 4 4 4的后缀链接只能指向 1 1 1 l a s t = 4 last=4 last=4
    在这里插入图片描述Ⅴ 将 ′ d ′ 'd' d扔进去,新建一个节点 c n t = 5 , l e n = 4 cnt=5,len=4 cnt=5,len=4
    l a s t last last开始跳后缀链接
    4 4 4没有 ′ c ′ 'c' c边,新建一条并指向 5 5 5,跳后缀链接到 1 1 1
    1 1 1没有 ′ c ′ 'c' c边,新建一条并指向 5 5 5
    此时已经到了根节点, 5 5 5的后缀链接只能指向 1 1 1 l a s t = 5 last=5 last=5
    在这里插入图片描述
    最简单的一种后缀自动机就完成了
    接下来就尝试一下进阶版

  • e . g . 2 e.g.2 e.g.2,构建 a b a b e ababe ababe的后缀自动机
    Ⅰ先搭建空源点, l a s t = 1 last=1 last=1
    在这里插入图片描述
    Ⅱ 加入 ′ a ′ 'a' a,新建一个节点 c n t = 2 , l e n [ 2 ] = l e n [ l a s t ] + 1 = 1 cnt=2,len[2]=len[last]+1=1 cnt=2,len[2]=len[last]+1=1
    1 1 1没有 ′ c ′ 'c' c边,新建一条并指向 2 2 2
    此时已经到了根节点, 2 2 2的后缀链接只能指向 1 1 1 l a s t = 2 last=2 last=2
    在这里插入图片描述
    Ⅲ 加入 ′ b ′ 'b' b,新建一个节点 c n t = 3 , l e n [ 3 ] = l e n [ l a s t ] + 1 = 2 cnt=3,len[3]=len[last]+1=2 cnt=3,len[3]=len[last]+1=2
    l a s t last last开始跳后缀链接
    2 2 2没有 ′ b ′ 'b' b边,新建一条并指向 3 3 3,跳后缀链接到 1 1 1
    1 1 1没有 ′ b ′ 'b' b边,新建一条并指向 3 3 3
    此时已经到了根节点, 3 3 3的后缀链接只能指向 1 1 1 l a s t = 3 last=3 last=3
    在这里插入图片描述
    Ⅳ 再加入 ′ a ′ 'a' a,新建一个节点 c n t = 4 , l e n [ 4 ] = l e n [ l a s t ] + 1 = 3 cnt=4,len[4]=len[last]+1=3 cnt=4,len[4]=len[last]+1=3
    l a s t last last开始跳后缀链接
    3 3 3没有 ′ a ′ 'a' a边,新建一条并指向 4 4 4,跳后缀链接到 1 1 1
    1 1 1有一条指向 2 2 2 ′ a ′ 'a' a边,满足 l e n [ 2 ] = l e n [ 1 ] + 1 len[2]=len[1]+1 len[2]=len[1]+1,则直接将 4 4 4后缀链接指向 2 2 2
    结束, l a s t = 4 last=4 last=4
    在这里插入图片描述
    Ⅴ 再加入 ′ b ′ 'b' b,新建一个节点 c n t = 5 , l e n [ 5 ] = l e n [ l a s t ] + 1 = 4 cnt=5,len[5]=len[last]+1=4 cnt=5,len[5]=len[last]+1=4
    l a s t last last开始跳后缀链接
    4 4 4没有 ′ b ′ 'b' b边,新建一条并指向 5 5 5,跳后缀链接到 2 2 2
    2 2 2有一条指向 3 3 3 ′ b ′ 'b' b边,满足 l e n [ 3 ] = l e n [ 2 ] + 1 len[3]=len[2]+1 len[3]=len[2]+1,直接将 5 5 5后缀链接指向 3 3 3
    结束, l a s t = 5 last=5 last=5
    在这里插入图片描述
    Ⅵ 加入新 ′ c ′ 'c' c,新建一个节点 c n t = 6 , l e n [ 6 ] = l e n [ l a s t ] + 1 = 5 cnt=6,len[6]=len[last]+1=5 cnt=6,len[6]=len[last]+1=5
    l a s t last last开始跳后缀链接
    5 5 5没有 ′ c ′ 'c' c边,新建一条并指向 6 6 6,跳后缀链接到 3 3 3
    3 3 3没有 ′ c ′ 'c' c边,新建一条并指向 6 6 6,跳后缀链接到 1 1 1
    1 1 1没有 ′ c ′ 'c' c边,新建一条并指向 6 6 6
    此时已到根节点, 6 6 6只能链接 1 1 1 l a s t = 6 last=6 last=6结束
    在这里插入图片描述这就是进阶版了,没有涉及到最终版的点复制
    最后让我们一起携手走进最终版的后缀自动机构造

  • e . g . 3 e.g.3 e.g.3,构建 c a b a b cabab cabab的后缀自动机
    Ⅰ 创造新源点, l a s t = 1 , c n t = 1 last=1,cnt=1 last=1,cnt=1
    在这里插入图片描述
    Ⅱ 加入 ′ c ′ 'c' c,新建一个节点 c n t = 2 , l e n [ 2 ] = l e n [ l a s t ] + 1 = 1 cnt=2,len[2]=len[last]+1=1 cnt=2,len[2]=len[last]+1=1
    l a s t last last开始跳后缀链接
    1 1 1没有 ′ c ′ 'c' c边,新建一条并指向 2 2 2
    此时已到根节点, 2 2 2只能链接 1 , l a s t = 2 1,last=2 1,last=2
    在这里插入图片描述
    Ⅲ 加入 ′ a ′ 'a' a,新建一个节点 c n t = 3 , l e n [ 3 ] = l e n [ l a s t ] + 1 = 2 cnt=3,len[3]=len[last]+1=2 cnt=3,len[3]=len[last]+1=2
    l a s t last last开始跳后缀链接
    2 2 2没有 ′ a ′ 'a' a边,新建一条并指向 3 3 3,跳后缀链接到 1 1 1
    1 1 1没有 ′ a ′ 'a' a边,新建一条并指向 3 3 3
    此时已到根节点, 3 3 3只能链接 1 , l a s t = 3 1,last=3 1,last=3
    在这里插入图片描述
    Ⅳ 加入 ′ b ′ 'b' b,新建一个节点 c n t = 4 , l e n [ 4 ] = l e n [ l a s t ] + 1 = 3 cnt=4,len[4]=len[last]+1=3 cnt=4,len[4]=len[last]+1=3
    l a s t last last开始跳后缀链接
    3 3 3没有 ′ b ′ 'b' b边,新建一条并指向 4 4 4,跳后缀链接到 1 1 1
    1 1 1没有 ′ a ′ 'a' a边,新建一条并指向 4 4 4
    此时已到根节点, 4 4 4只能链接 1 , l a s t = 4 1,last=4 1,last=4
    在这里插入图片描述
    Ⅴ 加入 ′ a ′ 'a' a,新建一个节点 c n t = 5 , l e n [ 5 ] = l e n [ l a s t ] + 1 = 4 cnt=5,len[5]=len[last]+1=4 cnt=5,len[5]=len[last]+1=4
    l a s t last last开始跳后缀链接
    4 4 4没有 ′ a ′ 'a' a边,新建一条并指向 5 5 5,跳后缀链接到 1 1 1
    1 1 1 ′ a ′ 'a' a边,指向 3 3 3,但是!!! l e n [ 3 ] ≠ l e n [ 1 ] + 1 len[3]≠len[1]+1 len[3]=len[1]+1,不能像进阶版直接链接,这里必须要点复制
    在这里插入图片描述新建一个 3 3 3的分身节点 c n t = 6 cnt=6 cnt=6
    3 3 3的所有信息(出入边)除了原字符串间的边(图中黑色边)全部修改为分点 6 6 6的边,直接覆盖
    并且 6 6 6成为 3 3 3的直接后缀链接,替代 1 1 1
    l e n [ 6 ] = l e n [ 1 ] + 1 = 1 len[6]=len[1]+1=1 len[6]=len[1]+1=1
    相当于 6 6 6做了 1 , 3 1,3 1,3后缀链之间的承接点,保证了每一条边上 l e n len len只会带来 + 1 +1 +1的影响
    5 5 5直接链接 6 6 6后结束, l a s t = 5 last=5 last=5
    在这里插入图片描述
    Ⅵ 加入 ′ b ′ 'b' b,新建节点 c n t = 7 cnt=7 cnt=7
    l a s t last last开始跳后缀链接
    5 5 5没有 ′ b ′ 'b' b边,新建一条指向 7 7 7,跳后缀链接到 6 6 6
    6 6 6有一条 ′ b ′ 'b' b边,指向 4 4 4,判断 l e n [ 4 ] ≠ l e n [ 6 ] + 1 len[4]≠len[6]+1 len[4]=len[6]+1
    在这里插入图片描述
    再次执行复制操作
    新建一个 4 4 4的分身节点 c n t = 8 cnt=8 cnt=8
    4 4 4的所有信息(出入边)除了原字符串间的边(图中黑色边)全部修改为分点 8 8 8的边,直接进行覆盖
    8 8 8成为 4 4 4的直接后缀链接, l e n [ 8 ] = l e n [ 6 ] + 1 = 2 len[8]=len[6]+1=2 len[8]=len[6]+1=2
    7 7 7直接链接 8 8 8后结束, l a s t = 7 last=7 last=7
    在这里插入图片描述


l e n [ x ] len[x] len[x]复制点的 l e n len len不等于被复制点的原后缀链接的 l e n + 1 len+1 len+1,而是谁触发的 l e n + 1 len+1 len+1


模板

struct node {
    
    
	int len; //长度
	int fa; //后缀链接
	int son[maxc]; //字符集大小 
}t[maxn];

模拟从主链的前一个开始跳后缀链接,并对于链接上的没有该字符边的每一个点都连出一条新字符边

while( pre && ! t[pre].son[c] ) t[pre].son[c] = now, pre = t[pre].fa;

跳到根,代表这是首个出现的字符,他只能链接最初的根节点了

if( ! pre ) t[now].fa = 1;

否则,如果路上找到了,满足 l e n len len的关系,直接后缀链接指过去即可

int u = t[pre].son[c];
if( t[u].len == t[pre].len + 1 ) t[now].fa = u;

复制该点,并进行有关该点的所有信息重改
①原点连出的点,新点也要连出
②连入原点的点,变成连入新点
③原点和新点间也需建立联系,新点是原点的后缀链接

else {
    
    
	int v = ++ tot;
	t[v] = t[u];//利用结构体巧妙将原点连出的点进行复制
	t[v].len = t[pre].len + 1;//由谁触发 len就是触发点len+1
	t[u].fa = t[now].fa = v;//原点与复制点与新建点的关系
	while( pre && t[pre].son[c] == u ) t[pre].son[c] = v, pre = t[pre].fa;//暴力复制修改连入原点的点
}

习题

洛谷后缀自动机模板题

  • code
#include <cstdio>
#include <vector>
#include <cstring>
using namespace std;
#define maxn 2000005
vector < int > G[maxn];
struct node {
    
    
	int fa, len;
	int son[30];
}t[maxn];
char s[maxn];
int last = 1, tot = 1;
long long ans;
int siz[maxn];

void insert( int c ) {
    
    
	int pre = last, now = last = ++ tot;
	siz[tot] = 1;
	t[now].len = t[pre].len + 1;
	while( pre && ! t[pre].son[c] ) t[pre].son[c] = now, pre = t[pre].fa;
	if( ! pre ) t[now].fa = 1;
	else {
    
    
		int u = t[pre].son[c];
		if( t[u].len == t[pre].len + 1 ) t[now].fa = u;
		else {
    
    
			int v = ++ tot;
			t[v] = t[u];
			t[v].len = t[pre].len + 1;
			t[u].fa = t[now].fa = v;
			while( pre && t[pre].son[c] == u ) t[pre].son[c] = v, pre = t[pre].fa;
		}
	}
}

void dfs( int u ) {
    
    
	for( int i = 0;i < G[u].size();i ++ ) {
    
    
		int v = G[u][i];
		dfs( v );
		siz[u] += siz[v];
	}
	if( siz[u] != 1 ) ans = max( ans, 1ll * siz[u] * t[u].len );
}

int main() {
    
    
	scanf( "%s", s );
	int len = strlen( s );
	for( int i = 0;i < len;i ++ ) insert( s[i] - 'a' );	
	for( int i = 2;i <= tot;i ++ ) G[t[i].fa].push_back( i );
	dfs( 1 );
	printf( "%lld", ans );
	return 0;
}

品酒大会

  • solution
    有一个 S A M SAM SAM常用结论:前缀 i , j i,j i,j最长公共后缀 = p a r e n t   t r e e =parent\ tree =parent tree上前缀 i , j i,j i,j分别指向的点 u , v u,v u,v l c a lca lca反映在后缀自动机上的节点代表的最长子串
    将本题的字符串倒过来建后缀自动机,在自动机上进行树上 d p dp dp,最后从后往前进行更新即可
  • code
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
using namespace std;
#define inf 0x7f7f7f7f
#define int long long
#define maxn 600005
struct node {
    
    
	int len, fa;
	int son[30];
}t[maxn];
vector < int > G[maxn];
int last = 1, cnt = 1, n;
char s[maxn];
int a[maxn], f[maxn], tot[maxn], siz[maxn], maxx[maxn], minn[maxn];

void insert( int x, int w ) {
    
    
	int pre = last, now = last = ++ cnt;
	siz[now] = 1, t[now].len = t[pre].len + 1;
	maxx[now] = minn[now] = w;
	while( pre && ! t[pre].son[x] ) t[pre].son[x] = now, pre = t[pre].fa;
	if( ! pre ) t[now].fa = 1;
	else {
    
    
		int u = t[pre].son[x];
		if( t[u].len == t[pre].len + 1 ) t[now].fa = u;
		else {
    
    
			int v = ++ cnt;
			maxx[v] = -inf, minn[v] = inf;
			t[v] = t[u];
			t[v].len = t[pre].len + 1;
			t[u].fa = t[now].fa = v;
			while( pre && t[pre].son[x] == u ) t[pre].son[x] = v, pre = t[pre].fa;
		}
	}
}

bool check( int u ) {
    
    
	return maxx[u] != -inf && minn[u] != inf;
}

void dfs( int u ) {
    
    
	for( int i = 0;i < G[u].size();i ++ ) {
    
    
		int v = G[u][i];
		dfs( v );
		tot[t[u].len] += siz[u] * siz[v];
		siz[u] += siz[v];
		if( check( u ) )
			f[t[u].len] = max( f[t[u].len], max( maxx[u] * maxx[v], minn[u] * minn[v] ) );
		maxx[u] = max( maxx[u], maxx[v] );
		minn[u] = min( minn[u], minn[v] );
	}
}

signed main() {
    
    
	memset( f, -0x7f, sizeof( f ) );
	scanf( "%d %s", &n, s + 1 );
	for( int i = 1;i <= n;i ++ )
		scanf( "%lld", &a[i] );
	for( int i = n;i;i -- ) insert( s[i] - 'a', a[i] );	
	for( int i = 2;i <= cnt;i ++ ) G[t[i].fa].push_back( i );
	dfs( 1 );
	for( int i = n - 1;~ i;i -- ) tot[i] += tot[i + 1], f[i] = max( f[i], f[i + 1] );
	for( int i = 0;i < n;i ++ )
		printf( "%lld %lld\n", tot[i], ( tot[i] ? f[i] : 0 ) );
	return 0;
}

[HEOI2015]最短不公共子串

  • solution
    做此题需要了解序列自动机
    然后就是很无脑的四个 b f s bfs bfs
    子串就是跑后缀自动机
    子序列就是跑序列自动机
  • code
#include <queue>
#include <cstdio>
#include <cstring>
using namespace std;
#define maxn 5000
char a[maxn], b[maxn];

struct SAM {
    
    
	struct node {
    
    
		int len, fa;
		int son[30];
	}t[maxn];
	int last, cnt;
	
	SAM() {
    
    
		last = cnt = 1;
	}
	
	void insert( int x ) {
    
    
		int pre = last, now = last = ++ cnt;
		t[now].len = t[pre].len + 1;
		while( pre && ! t[pre].son[x] ) t[pre].son[x] = now, pre = t[pre].fa;
		if( ! pre ) t[now].fa = 1;
		else {
    
    
			int u = t[pre].son[x];
			if( t[u].len == t[pre].len + 1 ) t[now].fa = u;
			else {
    
    
				int v = ++ cnt;
				t[v] = t[u];
				t[v].len = t[pre].len + 1;
				t[u].fa = t[now].fa = v;
				while( pre && t[pre].son[x] == u ) t[pre].son[x] = v, pre = t[pre].fa;
			}
		}
	}
	
}SamA, SamB;

struct SEQ {
    
    
	int nxt[maxn][30], last[30];
	
	SEQ() {
    
    
		memset( nxt, 0, sizeof( nxt ) );
		memset( last, 0, sizeof( last ) );
	}
	
	void build( int n, char *s ) {
    
    
		for( int i = n;~ i;i -- ) {
    
    
			for( int j = 0;j < 26;j ++ )
				if( last[j] ) nxt[i + 1][j] = last[j];
			if( i ) last[s[i] - 'a'] = i + 1;
		}
	}
	
}SeqA, SeqB;

struct node {
    
    
	int x, y, dep;
	node(){
    
    }
	node( int X, int Y, int Dep ) {
    
    
		x = X, y = Y, dep = Dep;
	}
};
queue < node > q;
bool vis[maxn][maxn];

void init() {
    
    
	memset( vis, 0, sizeof( vis ) );
	while( ! q.empty() ) q.pop();
	vis[1][1] = 1, q.push( node( 1, 1, 0 ) );
}

int bfs1() {
    
    
	init();
	while( ! q.empty() ) {
    
    
		node now = q.front(); q.pop();
		for( int i = 0;i < 26;i ++ ) {
    
    
			int sonA = SamA.t[now.x].son[i];
			int sonB = SamB.t[now.y].son[i];
			if( vis[sonA][sonB] ) continue;
			else if( sonA && ! sonB ) return now.dep + 1;
			else if( sonA && sonB ) {
    
    
				vis[sonA][sonB] = 1;
				q.push( node( sonA, sonB, now.dep + 1 ) );
			}
		}
	}
	return -1;
}

int bfs2() {
    
    
	init();
	while( ! q.empty() ) {
    
    
		node now = q.front(); q.pop();
		for( int i = 0;i < 26;i ++ ) {
    
    
			int sonA = SamA.t[now.x].son[i];
			int sonB = SeqB.nxt[now.y][i];
			if( vis[sonA][sonB] ) continue;
			else if( sonA && ! sonB ) return now.dep + 1;
			else if( sonA && sonB ) {
    
    
				vis[sonA][sonB] = 1;
				q.push( node( sonA, sonB, now.dep + 1 ) );
			}
		}
	}
	return -1;
}

int bfs3() {
    
    
	init();
	while( ! q.empty() ) {
    
    
		node now = q.front(); q.pop();
		for( int i = 0;i < 26;i ++ ) {
    
    
			int sonA = SeqA.nxt[now.x][i];
			int sonB = SamB.t[now.y].son[i];
			if( vis[sonA][sonB] ) continue;
			else if( sonA && ! sonB ) return now.dep + 1;
			else if( sonA && sonB ) {
    
    
				vis[sonA][sonB] = 1;
				q.push( node( sonA, sonB, now.dep + 1 ) );
			}
		}
	}
	return -1;
}

int bfs4() {
    
    
	init();
	while( ! q.empty() ) {
    
    
		node now = q.front(); q.pop();
		for( int i = 0;i < 26;i ++ ) {
    
    
			int sonA = SeqA.nxt[now.x][i];
			int sonB = SeqB.nxt[now.y][i];
			if( vis[sonA][sonB] ) continue;
			else if( sonA && ! sonB ) return now.dep + 1;
			else if( sonA && sonB ) {
    
    
				vis[sonA][sonB] = 1;
				q.push( node( sonA, sonB, now.dep + 1 ) );
			}
		}
	}
	return -1;
}

int main() {
    
    
	scanf( "%s %s", a + 1, b + 1 );
	int lena = strlen( a + 1 ), lenb = strlen( b + 1 );
	for( int i = 1;i <= lena;i ++ )
		SamA.insert( a[i] - 'a' );
	for( int i = 1;i <= lenb;i ++ )
		SamB.insert( b[i] - 'a' );
	SeqA.build( lena, a );
	SeqB.build( lenb, b );
	printf( "%d\n%d\n%d\n%d\n", bfs1(), bfs2(), bfs3(), bfs4() );
	return 0;
} 

字符串

  • solution

这题运用的思想主要是广义后缀自动机,即将多个字符串建在一个后缀自动机上
其实并没有什么新颖之处,只需在扩展的时候带一个这个字符属于哪个字符串的编号即可

假设已经建好了自动机,接下来考虑两个长度为 k k k的子串之间如何一一对应修改
这个时候如果将其放到 p a r e n t   t r e e parent\ tree parent tree上考虑的话,就简单了

其实可以猜想一下,刚开始我就想到了虚树的性质,即相邻两两配对
不难证明,的确应该相邻两个不同属类的子串配对

前缀 i , j i,j i,j最长公共后缀 = p a r e n t   t r e e =parent\ tree =parent tree上前缀 i , j i,j i,j分别指向的点 u , v u,v u,v l c a lca lca反映在后缀自动机上的节点代表的最长子串

也就是最后变成深搜一棵树的模样,记得特判可能 l c a lca lca代表的最长子串长度 ≥ k \ge k k
此时是不需要代价的

  • code
#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
#define maxn 600005
struct node {
    
    
	int len, fa;
	int son[30];
}t[maxn];
vector < int > G[maxn];
int n, k, last = 1, cnt = 1;
long long ans;
char a[maxn], b[maxn];
int type[maxn];
int tot[maxn][3];

void insert( int x, int s, int pos ) {
    
    
	int pre = last, now = last = ++ cnt;
	t[now].len = t[pre].len + 1;
	while( pre && ! t[pre].son[x] ) t[pre].son[x] = now, pre = t[pre].fa;
	if( ! pre ) t[now].fa = 1;
	else {
    
    
		int u = t[pre].son[x];	
		if( t[u].len == t[pre].len + 1 ) t[now].fa = u;
		else {
    
    
			int v = ++ cnt;
			t[v] = t[u];
			t[v].len = t[pre].len + 1;
			t[u].fa = t[now].fa = v;
			while( pre && t[pre].son[x] == u ) t[pre].son[x] = v, pre = t[pre].fa;
		}
	}
	if( pos >= k ) type[now] = s;
}

void dfs( int u ) {
    
    
	tot[u][type[u]] ++;
	for( int i = 0;i < G[u].size();i ++ ) {
    
    
		int v = G[u][i];
		dfs( v );
		tot[u][1] += tot[v][1];
		tot[u][2] += tot[v][2];
	}
	if( tot[u][1] >= tot[u][2] ) {
    
    
		int x = max( 0, k - t[u].len );
		ans += 1ll * x * tot[u][2];
		tot[u][1] -= tot[u][2];
		tot[u][2] = 0;
	}
	else {
    
    
		int x = max( 0, k - t[u].len );
		ans += 1ll * x * tot[u][1];
		tot[u][2] -= tot[u][1];
		tot[u][1] = 0;
	}
}

int main() {
    
    
	scanf( "%d %d %s %s", &n, &k, a + 1, b + 1 );
	reverse( a + 1, a + n + 1 );
	reverse( b + 1, b + n + 1 );
	for( int i = 1;i <= n;i ++ ) insert( a[i] - 'a', 1, i );
	for( int i = 1;i <= n;i ++ ) insert( b[i] - 'a', 2, i );
	for( int i = 2;i <= cnt;i ++ ) G[t[i].fa].push_back( i );
	dfs( 1 );
	printf( "%lld", ans );
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Emm_Titan/article/details/111994874
今日推荐