P3804 【模板】后缀自动机 (SAM)

给定一个只包含小写字母的字符串SS,
请你求出 S 的所有出现次数不为 1 的子串的出现次数乘上该子串长度的最大值。

模板题、遍历所有点即可、分裂节点size初始不记

#include <bits/stdc++.h>
const int maxn=2e6+50;
using namespace std;
int head[maxn<<1],nex[maxn<<1],to[maxn<<1],ecnt;
int size[maxn];
struct node {
	int ch[26];
	int len,fa;
} a[maxn<<1];
int pre=1,tot=1;
void add(int x,int y) {
	to[++ecnt]=y;
	nex[ecnt]=head[x];
	head[x]=ecnt;
}
void insert(int c) {
	int p=pre,np=pre=++tot;
	size[tot]=1;
	a[np].len=a[p].len+1;
	for(; p&&!a[p].ch[c]; p=a[p].fa) a[p].ch[c]=np;
	if(!p) a[np].fa=1;
	else {
		int q=a[p].ch[c];
		if(a[q].len==a[p].len+1) a[np].fa=q;
		else {
			int nq=++tot;
			a[nq]=a[q];
			a[nq].len=a[p].len+1;
			a[q].fa=a[np].fa=nq;
			for(; p&&a[p].ch[c]==q; p=a[p].fa) a[p].ch[c]=nq;
		}
	}
}
int ans=0;
void dfs(int x) {
	for(int i=head[x]; i; i=nex[i]) {
		int y=to[i];
		dfs(y);
		size[x]+=size[y];
	}
	if(size[x]!=1)ans=max(ans,size[x]*a[x].len);
}
char s[maxn];
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++)add(a[i].fa,i);
	dfs(1);
	printf("%d\n",ans);
}

SP1811 LCS - Longest Common Substring

输入两个字符串,输出它们的最长公共子串长度,若不存在公共子串则输出 0。

对第一个串建立后缀自动机,第二个串在自动机上跑即可,如匹配失败、则跳parent树直到后一位能匹配为止,注意若跳到0节点,则回到1节点并设length为0

#include <bits/stdc++.h>
const int maxn=1e6+50;
using namespace std;
int head[maxn<<1],nex[maxn<<1],to[maxn<<1],ecnt;
int size[maxn];
long long ans=0;
struct node {
	int ch[26];
	int len,fa;
} a[maxn<<1];
int pre=1,tot=1;
void add(int x,int y) {
	to[++ecnt]=y;
	nex[ecnt]=head[x];
	head[x]=ecnt;
}
void init() {
	pre=tot=1;
	for(int i=0; i<26; i++) {
		a[1].ch[i]=0;
	}
	ans=0;
	a[1].len=0;
	a[1].fa=0;
}
inline int newnode() {
	++tot;
	a[tot].len=0;
	a[tot].fa=0;
	for(int i=0; i<26; i++) {
		a[tot].ch[i]=0;
	}
	return tot;
}
void insert(int c) {
	int p=pre,np=pre=newnode();
	size[tot]=1;
	a[np].len=a[p].len+1;
	for(; p&&!a[p].ch[c]; p=a[p].fa) a[p].ch[c]=np;
	if(!p) a[np].fa=1;
	else {
		int q=a[p].ch[c];
		if(a[q].len==a[p].len+1) a[np].fa=q;
		else {
			int nq=newnode();
			a[nq]=a[q];
			a[nq].len=a[p].len+1;
			a[q].fa=a[np].fa=nq;
			for(; p&&a[p].ch[c]==q; p=a[p].fa) a[p].ch[c]=nq;
		}
	}
}
char s[maxn];
char t[maxn];
int main() {
	scanf("%s",s);
	scanf("%s",t);
	int len=strlen(s);
	int len1=strlen(t);
	init();
	for(int i=0; i<len; i++) insert(s[i]-'a');
	int pos=1;
	int length=0;
	int maxx=0;
	for(int i=0; i<len1; i++) {
		if(a[pos].ch[t[i]-'a']) {
			length++;
			pos=a[pos].ch[t[i]-'a'];
			maxx=max(maxx,length);
		} else {
			while(pos&&!a[pos].ch[t[i]-'a'])pos=a[pos].fa;
			if(pos==0) {
				pos=1;
				length=0;
			} else {
				length=a[pos].len+1;
                pos=a[pos].ch[t[i]-'a'];
			}
		}
	}
	printf("%d\n",maxx);
}

SP1812 LCS2 - Longest Common Substring II

给定一些字符串,求出它们的最长公共子串 输入格式 输入至多10行,每行包含不超过100000个的小写字母,表示一个字符串 输出格式 一个数,最长公共子串的长度 若不存在最长公共子串,请输出0。

多串LCS,依然在第一串上建立SAM,之后的每个串去在SAM上匹配,利用\(mx\)数组维护单串匹配时每个节点匹配的最大值,利用\(mi\)数组维护每个节点匹配最小值,最后对取\(mi\)最大即可,注意若
某个节点已经被匹配了,那么他的父节点一定也能被匹配,所以每次匹配后需要将子节点转移向父节点

#include <bits/stdc++.h>
const int maxn=1e6+50;
using namespace std;
int head[maxn<<1],nex[maxn<<1],to[maxn<<1],ecnt;
int size[maxn];
long long ans=0;
struct node {
	int ch[26];
	int len,fa;
} a[maxn<<1];
int pre=1,tot=1;
int mx[maxn];
int mi[maxn];
void add(int x,int y) {
	to[++ecnt]=y;
	nex[ecnt]=head[x];
	head[x]=ecnt;
}
void init() {
	pre=tot=1;
	for(int i=0; i<26; i++) {
		a[1].ch[i]=0;
	}
	ans=0;
	a[1].len=0;
	a[1].fa=0;
}
inline int newnode() {
	++tot;
	a[tot].len=0;
	a[tot].fa=0;
	for(int i=0; i<26; i++) {
		a[tot].ch[i]=0;
	}
	return tot;
}
inline void insert(int c) {
	int p=pre,np=pre=newnode();
	size[tot]=1;
	a[np].len=a[p].len+1;
	for(; p&&!a[p].ch[c]; p=a[p].fa) a[p].ch[c]=np;
	if(!p) a[np].fa=1;
	else {
		int q=a[p].ch[c];
		if(a[q].len==a[p].len+1) a[np].fa=q;
		else {
			int nq=newnode();
			a[nq]=a[q];
			a[nq].len=a[p].len+1;
			a[q].fa=a[np].fa=nq;
			for(; p&&a[p].ch[c]==q; p=a[p].fa) a[p].ch[c]=nq;
		}
	}
}

int len;
int cnt[maxn];
int ran[maxn];
inline void prepare() {
	for(int i=1; i<=tot; i++) cnt[a[i].len]++;
	for(int i=1; i<=len; i++) cnt[i]+=cnt[i-1];
	for(int i=tot; i; i--) ran[cnt[a[i].len]--]=i;
}
char s[maxn];
char t[maxn];
int main() {
	scanf("%s",s);
	len=strlen(s);
	init();
	for(int i=0; i<len; i++) insert(s[i]-'a');
	prepare();
	int maxx=0;
	for(int i=2; i<=tot; i++) {
		mi[i]=0x3f3f3f3f;
	}
	while(~scanf("%s",t)) {
		for(int i=2; i<=tot; i++) {
			mx[i]=0;
		}
		int len1=strlen(t);
		int pos=1;
		int length=0;
		for(int i=0; i<len1; i++) {
			if(a[pos].ch[t[i]-'a']) {
				length++;
				pos=a[pos].ch[t[i]-'a'];
				mx[pos]=max(mx[pos],length);
			} else {
				while(pos&&!a[pos].ch[t[i]-'a'])pos=a[pos].fa;
				if(pos==0) {
					pos=1;
					length=0;
				} else {
					length=a[pos].len+1;
					pos=a[pos].ch[t[i]-'a'];
					mx[pos]=max(mx[pos],length);
				}
			}
		}
		for(int i=tot; i>=2; --i) {
			if(mx[ran[i]])mx[a[ran[i]].fa]=a[a[ran[i]].fa].len;
		}
		for(int i=2; i<=tot; ++i) {
			mi[i]=min(mx[i],mi[i]);
		}

	}
	for(int i=2; i<=tot; ++i) {
		maxx=max(maxx,mi[i]);
	}
	printf("%d\n",maxx);
}

P3975 [TJOI2015]弦论

求字典序第K小子串

t为0则表示不同位置的相同子串算作一个,t 为 1 则表示不同位置的相同子串算作多个。

#include <bits/stdc++.h>
const int maxn=1e6+50;
using namespace std;
int head[maxn<<1],nex[maxn<<1],to[maxn<<1],ecnt;
int size[maxn];
long long ans=0;
int dp[maxn];
struct node {
	int ch[26];
	int len,fa;
} a[maxn<<1];
int pre=1,tot=1;
inline void add(int x,int y) {
	to[++ecnt]=y;
	nex[ecnt]=head[x];
	head[x]=ecnt;
}
inline void init() {
	pre=tot=1;
	for(register int i=0; i<26; i++) {
		a[1].ch[i]=0;
	}
	ans=0;
	a[1].len=0;
	a[1].fa=0;
}
inline int newnode() {
	++tot;
	a[tot].len=0;
	a[tot].fa=0;
	for(register int i=0; i<26; i++) {
		a[tot].ch[i]=0;
	}
	return tot;
}
inline void insert(int c) {
	int p=pre,np=pre=newnode();
	size[tot]=1;
	a[np].len=a[p].len+1;
	for(; p&&!a[p].ch[c]; p=a[p].fa) a[p].ch[c]=np;
	if(!p) a[np].fa=1;
	else {
		int q=a[p].ch[c];
		if(a[q].len==a[p].len+1) a[np].fa=q;
		else {
			int nq=newnode();
			a[nq]=a[q];
			a[nq].len=a[p].len+1;
			a[q].fa=a[np].fa=nq;
			for(; p&&a[p].ch[c]==q; p=a[p].fa) a[p].ch[c]=nq;
		}
	}
}
void dfs(int x) {
	for(int i=head[x]; i; i=nex[i]) {
		int y=to[i];
		dfs(y);
		size[x]+=size[y];
	}
}
void ddfs(int x) {//判断以这个串为前缀的有多少个串 
	if(dp[x])
		return;
	dp[x]=size[x];
	for(int i=0; i<26; i++) {
		if(a[x].ch[i]) {
			ddfs(a[x].ch[i]);
			dp[x]+=dp[a[x].ch[i]];
		}
	}
}

char s[maxn];
int main() {
	scanf("%s",s);
	int len=strlen(s);
	init();
	for(register int i=0; i<len; i++) insert(s[i]-'a');
	for(int i=2; i<=tot; i++)add(a[i].fa,i);
	int t,k;
	scanf("%d %d",&t,&k);
	if(t==0) {
		for(int i=2; i<=tot; i++)size[i]=1;//如果不算重复串,每串size为1 
	} else {
		dfs(1);//否则利用重复子串个数规律求个数 
	}
	size[1]=0;//空串本身个数为0 
	ddfs(1);
	if(dp[1]<k) {
		printf("-1\n");
	} else {
		int pos=1;
		while(k>0) {
			for(int i=0; i<26; i++) {
				int id=a[pos].ch[i];
				if(id) { 
					if(dp[id]>=k) {//如果这个串总数大于等于k,那么首先他本身是一定在范围内的,减去它本身并输出 
						k-=size[id];
						pos=id;
						cout<<char(i+'a');
						break;
					} else {
						k-=dp[id];
					}
				}
				if(k<=0)break;
			}
		}
	}

}

P3181 [HAOI2016]找相同字符

给定两个字符串,求出在两个字符串中各取出一个子串使得这两个子串相同的方案数。两个方案不同当且仅当这两个子串中有一个位置不同。

对第一个串建立SAM,预处理每个节点的贡献、第二个串去匹配,如果匹配到了某个节点,累加贡献即可

#include <bits/stdc++.h>
const int maxn=1e6+50;
using namespace std;
#define int long long
int head[maxn<<1],nex[maxn<<1],to[maxn<<1],ecnt;
int size[maxn];
int ans=0;
struct node {
	int ch[26];
	int len,fa;
} a[maxn<<1];
int pre=1,tot=1;
int mx[maxn];
int mi[maxn];
inline void add(int x,int y) {
	to[++ecnt]=y;
	nex[ecnt]=head[x];
	head[x]=ecnt;
}
inline void init() {
	pre=tot=1;
	for(register int i=0; i<26; i++) {
		a[1].ch[i]=0;
	}
	ans=0;
	a[1].len=0;
	a[1].fa=0;
}
inline int newnode() {
	++tot;
	a[tot].len=0;
	a[tot].fa=0;
	for(int i=0; i<26; i++) {
		a[tot].ch[i]=0;
	}
	return tot;
}
inline void insert(int c) {
	int p=pre,np=pre=newnode();
	size[tot]=1;
	a[np].len=a[p].len+1;
	for(; p&&!a[p].ch[c]; p=a[p].fa) a[p].ch[c]=np;
	if(!p) a[np].fa=1;
	else {
		int q=a[p].ch[c];
		if(a[q].len==a[p].len+1) a[np].fa=q;
		else {
			int nq=newnode();
			a[nq]=a[q];
			a[nq].len=a[p].len+1;
			a[q].fa=a[np].fa=nq;
			for(; p&&a[p].ch[c]==q; p=a[p].fa) a[p].ch[c]=nq;
		}
	}
}
int sum[maxn];
inline void dfs(int x) {
	for(register int i=head[x]; i; i=nex[i]) {
		int y=to[i];
		dfs(y);
		size[x]+=size[y];
	}
}
inline void bfs() {
	queue<int>q;
	q.push(1);
	size[0]=size[1]=0;
	while(!q.empty()) {
		int x=q.front();
		q.pop();
		for(int i=head[x]; i; i=nex[i]) {
			int y=to[i];
			sum[y]=sum[x]+size[y]*(a[y].len-a[x].len);
			q.push(y);
		}
	}
}
char s[maxn];
char t[maxn];
signed main() {
	scanf("%s",s);
	int len=strlen(s);
	init();
	for(register int i=0;i<len; i++) insert(s[i]-'a');
	for(register int i=2; i<=tot; i++)add(a[i].fa,i);
	dfs(1);
	int res=0;
	scanf("%s",t);
	bfs();
	int len1=strlen(t);
	int pos=1;
	int length=0;
	for(register int i=0; i<len1; i++) {
		if(a[pos].ch[t[i]-'a']) {
			length++;
			pos=a[pos].ch[t[i]-'a'];
		} else {
			while(pos&&!a[pos].ch[t[i]-'a'])pos=a[pos].fa;
			if(pos==0) {
				pos=1;
				length=0;
			} else {
				length=a[pos].len+1;
				pos=a[pos].ch[t[i]-'a'];
			}
		}
		res+=sum[a[pos].fa]+size[pos]*(length-a[a[pos].fa].len);
	}
	printf("%lld\n",res);

}

SP8222 NSUBSTR - Substrings

你得到了一个最多由 250000 个小写拉丁字母组成的字符串 S。定义 F(x) 为 S 的某些长度为 x 的子串在 S 中的最大出现次数。即 F(x)=max{times(T)},满足 T 是 S 的子串且 |T|=x。例如当 S=ababa 时 F(3)=2,因为 S 中有一个出现 2 次的子串 abaaba。 输出i属于1~n的F(i)

因为长度小的出现次数一定大于等于长度大的,所以我们只要维护每个点的长度最大出现次数,最后转移即可

#include <bits/stdc++.h>
const int maxn=1e6+50;
using namespace std;
int head[maxn<<1],nex[maxn<<1],to[maxn<<1],ecnt;
int size[maxn];
long long ans=0;
struct node {
	int ch[26];
	int len,fa;
} a[maxn<<1];
int pre=1,tot=1;
void add(int x,int y) {
	to[++ecnt]=y;
	nex[ecnt]=head[x];
	head[x]=ecnt;
}
void init() {
	pre=tot=1;
	for(int i=0; i<26; i++) {
		a[1].ch[i]=0;
	}
	ans=0;
	a[1].len=0;
	a[1].fa=0;
}
inline int newnode() {
	++tot;
	a[tot].len=0;
	a[tot].fa=0;
	for(int i=0; i<26; i++) {
		a[tot].ch[i]=0;
	}
	return tot;
}
void insert(int c) {
	int p=pre,np=pre=newnode();
	size[tot]=1;
	a[np].len=a[p].len+1;
	for(; p&&!a[p].ch[c]; p=a[p].fa) a[p].ch[c]=np;
	if(!p) a[np].fa=1;
	else {
		int q=a[p].ch[c];
		if(a[q].len==a[p].len+1) a[np].fa=q;
		else {
			int nq=newnode();
			a[nq]=a[q];
			a[nq].len=a[p].len+1;
			a[q].fa=a[np].fa=nq;
			for(; p&&a[p].ch[c]==q; p=a[p].fa) a[p].ch[c]=nq;
		}
	}
}
void dfs(int x) {
	for(int i=head[x]; i; i=nex[i]) {
		int y=to[i];
		dfs(y);
		size[x]+=size[y];
	}
}
int len;
int dp[maxn];
int ran[maxn];
int cnt[maxn];
inline void prepare() {
	for(int i=1; i<=tot; i++) cnt[a[i].len]++;
	for(int i=1; i<=len; i++) cnt[i]+=cnt[i-1];
	for(int i=tot; i; i--) ran[cnt[a[i].len]--]=i;
	size[1]=size[0]=0;
	for(int i=tot; i; i--){
		dp[a[ran[i]].len]=max(dp[a[ran[i]].len],size[ran[i]]);
	}
    for(int i=tot;i;i--){
    	dp[i]=max(dp[i+1],dp[i]);
	}
}
char s[maxn];
int main() {
	scanf("%s",s);
	len=strlen(s);
	init();
	int maxx=0;
	for(int i=0; i<len; i++) insert(s[i]-'a');
	for(int i=2; i<=tot; i++)add(a[i].fa,i);
	dfs(1);
	prepare();
    for(int i=1;i<=len;i++){
    	cout<<dp[i]<<endl;
	}
}

P2463 [SDOI2008] Sandy 的卡片

求最长的两个子串长度相同且一个串的全部元素加上一个数就会变成另一个串。

我们考虑两两作差,即可转换为最长多串LCS,结果+1即可

#include <bits/stdc++.h>
const int maxn=1e4+50;
#define int long long
using namespace std;
struct node {
	map<int,int>ch;
	int len,fa;
} a[maxn<<1];
int pre=1,tot=1;
int mx[maxn];
int mi[maxn];
void init() {
	pre=tot=1;
	a[1].len=0;
	a[1].fa=0;
}
inline int newnode() {
	++tot;
	a[tot].len=0;
	a[tot].fa=0;
	return tot;
}
inline void insert(int c) {
	int p=pre,np=pre=newnode();
	a[np].len=a[p].len+1;
	for(; p&&!a[p].ch[c]; p=a[p].fa) a[p].ch[c]=np;
	if(!p) a[np].fa=1;
	else {
		int q=a[p].ch[c];
		if(a[q].len==a[p].len+1) a[np].fa=q;
		else {
			int nq=newnode();
			a[nq]=a[q];
			a[nq].len=a[p].len+1;
			a[q].fa=a[np].fa=nq;
			for(; p&&a[p].ch[c]==q; p=a[p].fa) a[p].ch[c]=nq;
		}
	}
}
int id[maxn];
int n;
int cnt[maxn];
inline void prepare() {
	for (int i=1; i<=tot; i++) cnt[a[i].len]++;
	for (int i=1; i<=n; i++) cnt[i]+=cnt[i-1];
	for (int i=1; i<=tot; i++) id[cnt[a[i].len]--]=i;
}
char s[maxn];
char t[maxn];
int b[maxn];
int c[maxn];
signed main() {
	int T;
	cin>>T;
	T--;
	cin>>n;
	for(int i=0; i<n; i++) {
		cin>>b[i];
	}
	init();
	for(int i=0; i<n-1; i++) {
		insert(b[i+1]-b[i]);
	}
	prepare();
	int maxx=0;
	for(int i=2; i<=tot; i++) {
		mi[i]=0x3f3f3f3f;
	}
	while(T--) {
		for(int i=2; i<=tot; i++) {
			mx[i]=0;
		}
		cin>>n;
		for(int i=0; i<n; i++) {
			cin>>b[i];
		}
		int pos=1;
		int length=0;
		for(int i=0; i<n-1; i++) {
			if(a[pos].ch[b[i+1]-b[i]]) {
				length++;
				pos=a[pos].ch[b[i+1]-b[i]];
			} else {
				while(pos&&!a[pos].ch[b[i+1]-b[i]])pos=a[pos].fa;
				if(pos==0) {
					pos=1;
					length=0;
				} else {
					length=a[pos].len+1;
					pos=a[pos].ch[b[i+1]-b[i]];
				}
			}
			mx[pos]=max(mx[pos],length);
		}
		for(int i=tot; i>=2; --i) {
			if(mx[id[i]])mx[a[id[i]].fa]=a[a[id[i]].fa].len;
		}
		for(int i=2; i<=tot; ++i) {
			mi[i]=min(mx[i],mi[i]);
		}
	}
	for(int i=2; i<=tot; ++i) {
		maxx=max(maxx,mi[i]);
	}
	printf("%lld\n",maxx+1);
}

P4248 [AHOI2013]差异

给定一个串、求 1<=i<j<=n 的初始位置后缀的所有最长前缀之和

本质是翻转原串,这样两个后缀的LCP,就是它们的LCA
即求所有两两LCA相同的串,贡献为\(size[i]*size[j] * len[lca]\)
因为实际上对于一个集合,两两相乘相加、实际上可以转化为\(size[x]*size[y]\),然后\(size[x]+=size[y]\),所以建边dfs即可,

#include <bits/stdc++.h>
const int maxn=1e6+50;
using namespace std;
#define int long long
int head[maxn<<1],nex[maxn<<1],to[maxn<<1],ecnt;
int size[maxn];
int ans=0;
struct node {
	int ch[26];
	int len,fa;
} a[maxn<<1];
int pre=1,tot=1;
inline void add(int x,int y) {
	to[++ecnt]=y;
	nex[ecnt]=head[x];
	head[x]=ecnt;
}
inline void init() {
	pre=tot=1;
	for(register int i=0; i<26; i++) {
		a[1].ch[i]=0;
	}
	ans=0;
	a[1].len=0;
	a[1].fa=0;
}
inline int newnode() {
	++tot;
	a[tot].len=0;
	a[tot].fa=0;
	for(int i=0; i<26; i++) {
		a[tot].ch[i]=0;
	}
	return tot;
}
inline void insert(int c) {
	int p=pre,np=pre=newnode();
	size[tot]=1;
	a[np].len=a[p].len+1;
	for(; p&&!a[p].ch[c]; p=a[p].fa) a[p].ch[c]=np;
	if(!p) a[np].fa=1;
	else {
		int q=a[p].ch[c];
		if(a[q].len==a[p].len+1) a[np].fa=q;
		else {
			int nq=newnode();
			a[nq]=a[q];
			a[nq].len=a[p].len+1;
			a[q].fa=a[np].fa=nq;
			for(; p&&a[p].ch[c]==q; p=a[p].fa) a[p].ch[c]=nq;
		}
	}
}
int sum[maxn];
inline void dfs(int x) {
	for(register int i=head[x]; i; i=nex[i]) {
		int y=to[i];
		dfs(y);
		ans+=size[x]*size[y]*a[x].len;
		cout<<ans<<endl;
		size[x]+=size[y];
	}
}
char s[maxn];
char t[maxn];
signed main() {
	scanf("%s",s);
	int len=strlen(s);
	init();
	for(register int i=len-1; i>=0; i--) insert(s[i]-'a');
	for(register int i=2; i<=tot; i++)add(a[i].fa,i);
	dfs(1);
	int res=0;
	cout<<len*(len+1)*(len-1)/2-2*ans<<endl;
}

P2178 [NOI2015] 品酒大会

对于第一个问题、对每个LCP的长度的节点维护贡献,对于第二个问题,对每个LCP长度的节点维护最大值、次大值、最小值、次小值、则结果为最大值次大值或最小值次小值

#include <bits/stdc++.h>
const int maxn=1e6+50;
using namespace std;
#define int long long
int head[maxn<<1],nex[maxn<<1],to[maxn<<1],ecnt;
const int inf=0x3f3f3f3f;
const int INF=0x3f3f3f3f3f3f3f3f;
int size[maxn];
int ans=0;
struct node {
	int ch[26];
	int len,fa;
} a[maxn<<1];
int pre=1,tot=1;
int mx1[maxn];
int mx2[maxn];
int mn1[maxn];
int mn2[maxn];
int sum[maxn];
int res[maxn];
int b[maxn];
inline void add(int x,int y) {
	to[++ecnt]=y;
	nex[ecnt]=head[x];
	head[x]=ecnt;
}
inline void init() {
	pre=tot=1;
	for(register int i=0; i<26; i++) {
		a[1].ch[i]=0;
	}
	ans=0;
	a[1].len=0;
	a[1].fa=0;
}
inline int newnode() {
	++tot;
	a[tot].len=0;
	a[tot].fa=0;
	for(int i=0; i<26; i++) {
		a[tot].ch[i]=0;
	}
	return tot;
}
inline void insert(int c,int i) {
	int p=pre,np=pre=newnode();
	size[tot]=1;
	a[np].len=a[p].len+1;
	mx1[np]=mn1[np]=b[i];
	mx2[np]=-inf;
	mn2[np]=inf;
	for(; p&&!a[p].ch[c]; p=a[p].fa) a[p].ch[c]=np;
	if(!p) a[np].fa=1;
	else {
		int q=a[p].ch[c];
		if(a[q].len==a[p].len+1) a[np].fa=q;
		else {
			int nq=newnode();
			mx1[nq]=mx2[nq]=-inf;
			mn1[nq]=mn2[nq]=inf;
			a[nq]=a[q];
			a[nq].len=a[p].len+1;
			a[q].fa=a[np].fa=nq;
			for(; p&&a[p].ch[c]==q; p=a[p].fa) a[p].ch[c]=nq;
		}
	}
}
inline void updatemx(int x, int delta) {
	if(delta>mx1[x]) mx2[x]=mx1[x], mx1[x]=delta;
	else if(delta>mx2[x]) mx2[x]=delta;
}
inline void updatemn(int x, int delta) {
	if(delta<mn1[x]) mn2[x]=mn1[x], mn1[x]=delta;
	else if(delta<mn2[x]) mn2[x]=delta;
}
inline void dfs(int x) {
	for(register int i=head[x]; i; i=nex[i]) {
		int y=to[i];
		dfs(y);
		sum[a[x].len]+=size[x]*size[y];
		size[x]+=size[y];
		updatemx(x, mx1[y]);
		updatemx(x, mx2[y]);
		updatemn(x, mn1[y]);
		updatemn(x, mn2[y]);
	}
	if(size[x]<=1)return;
	res[a[x].len]=max(res[a[x].len], max(mx1[x]*mx2[x], mn1[x]*mn2[x]));
}
char s[maxn];
char t[maxn];
signed main() {
//	freopen("P2178_7.in.txt","r",stdin);
	int n;
	scanf("%lld",&n);
	scanf("%s",s);
	int len=strlen(s);
	init();
	for(int i=0; i<n; i++) {
		scanf("%lld",&b[i]);
	}
	for(int i=len-1; i>=0; i--) insert(s[i]-'a',i);
	for(int i=2; i<=tot; i++)add(a[i].fa,i);
	mx1[1]=mx2[1]=-inf;
	mn1[1]=mn2[1]=inf;
	for(int i=0; i<=n; i++) {
		res[i]=-INF;
	}
	dfs(1);
	for(int i=n-1; i>=0; i--) {
		sum[i]+=sum[i+1];

			res[i]=max(res[i],res[i+1]);
		
	}
	for(int i=0; i<n; i++) {
		if(res[i]==-INF)res[i]=0;
		printf("%lld %lld\n",sum[i],res[i]);
	}

}

CF873F Forbidden Indices

0 代表这个位置不是被禁止的位置, 1 代表这个位置是被禁止的位置。
若 aa 是原字符串的一个子串,你需要求 f(a)*∣a∣ 的最大值。
其中 |a| 代表子串 aa 的长度,f(a) 代表不在被禁止位置结束的子串 a 的出现次数。

若某位为0则设这位size为0即可

#include <bits/stdc++.h>
const int maxn=2e5+50;
//#define int long long
using namespace std;
int head[maxn<<1],nex[maxn<<1],to[maxn<<1],ecnt;
int vis[maxn<<1];
int siz[maxn<<1];
long long ans=0;
struct node {
	int ch[26];
	int len,fa;
	int pos;
} a[maxn<<1];
int pre=1,tot=1;
void add(int x,int y) {
	to[++ecnt]=y;
	nex[ecnt]=head[x];
	head[x]=ecnt;
}
void init() {
	pre=tot=1;
	siz[1]=0;
	for(int i=0; i<26; i++) {
		a[1].ch[i]=0;
	}
	ans=0;
	a[1].len=0;
	a[1].fa=0;
}
inline int newnode() {
	++tot;
	a[tot].len=0;
	a[tot].fa=0;
	siz[tot]=0;
	for(int i=0; i<26; i++) {
		a[tot].ch[i]=0;
	}
	return tot;
}
void insert(int c,int pos) {
	int p=pre,np=pre=newnode();
	if(!pos){
	siz[tot]=1;}
	a[np].len=a[p].len+1;
	for(; p&&!a[p].ch[c]; p=a[p].fa) a[p].ch[c]=np;
	if(!p) a[np].fa=1;
	else {
		int q=a[p].ch[c];
		if(a[q].len==a[p].len+1) a[np].fa=q;
		else {
			int nq=newnode();
			a[nq]=a[q];
			a[nq].len=a[p].len+1;
			a[q].fa=a[np].fa=nq;
			for(; p&&a[p].ch[c]==q; p=a[p].fa) a[p].ch[c]=nq;
		}
	}
}
int ok[maxn];
long long res=0;
void dfs(int x) {
	for(int i=head[x]; i; i=nex[i]) {
		int y=to[i];
		dfs(y);
		siz[x]+=siz[y];
	}
	res=max(res,1ll*a[x].len*siz[x]);

}
string s;
string ss;
int n;
signed main() {
	init();
	cin>>n;
	cin>>s;
	cin>>ss;
	for(int i=0; i<n; i++)ok[i]=ss[i]-'0';
	int len=s.length();
	for(int i=0; i<len; i++) insert(s[i]-'a',ok[i]);
	for(int i=2; i<=tot; i++)add(a[i].fa,i);
	dfs(1);
	cout<<res<<endl;
}

CF235C Cyclical Quest

给定一个主串S和n个询问串,求每个询问串的所有循环同构在主串中出现的次数总和。

循环同构串即长度相同,如ABC、BCA即为一个循环同构串
我们考虑建立SAM,对每个询问串、将它变为原串两倍,然后用这个串去匹配SAM上长度为询问串原串长度的子串,如果长度等于length,计算贡献,并且找到删去当前第一个字符串的子串所处的位置
对于这个length-1长度的子串,实际上我们只要查询length-1是否大于当前结点父亲结点的len,如大于、说明还在当前结点,若小于、说明该串位于父亲结点,则转到父亲结点

#include <bits/stdc++.h>
const int maxn=1e6+50;
//#define int long long
using namespace std;
int head[maxn<<1],nex[maxn<<1],to[maxn<<1],ecnt;
int vis[maxn<<1];
int siz[maxn<<1];
long long ans=0;
struct node {
	int ch[26];
	int len,fa;
} a[maxn<<1];
int pre=1,tot=1;
void add(int x,int y) {
	to[++ecnt]=y;
	nex[ecnt]=head[x];
	head[x]=ecnt;
}
void init() {
	pre=tot=1;
	for(int i=0; i<26; i++) {
		a[1].ch[i]=0;
	}
	ans=0;
	a[1].len=0;
	a[1].fa=0;
}
inline int newnode() {
	++tot;
	a[tot].len=0;
	a[tot].fa=0;
	for(int i=0; i<26; i++) {
		a[tot].ch[i]=0;
	}
	return tot;
}
void insert(int c) {
	int p=pre,np=pre=newnode();
	siz[tot]=1;
	a[np].len=a[p].len+1;
	for(; p&&!a[p].ch[c]; p=a[p].fa) a[p].ch[c]=np;
	if(!p) a[np].fa=1;
	else {
		int q=a[p].ch[c];
		if(a[q].len==a[p].len+1) a[np].fa=q;
		else {
			int nq=newnode();
			a[nq]=a[q];
			a[nq].len=a[p].len+1;
			a[q].fa=a[np].fa=nq;
			for(; p&&a[p].ch[c]==q; p=a[p].fa) a[p].ch[c]=nq;
		}
	}
}
void dfs(int x) {
	for(int i=head[x]; i; i=nex[i]) {
		int y=to[i];
		dfs(y);
		siz[x]+=siz[y];
	}
}
string s;
signed main() {
	init();
	cin>>s;
	int len=s.length();
	for(int i=0; i<len; i++) insert(s[i]-'a');
	for(int i=2; i<=tot; i++)add(a[i].fa,i);
	dfs(1);
	int q;
	cin>>q;
	for(int k=1; k<=q; k++) {
		long long res=0;
		cin>>s;
		len=s.length();
		s+=s;
		int l=0,r=0;
		int pos=1;
		int length=0;
		for(int i=0; i<2*len; i++) {
			if(a[pos].ch[s[i]-'a']) {
				length++;
				pos=a[pos].ch[s[i]-'a'];
				if(length==len) {
					if(vis[pos]!=k) {
						vis[pos]=k;
						res+=siz[pos];
					}
					if(length-1<=a[a[pos].fa].len) {
						pos=a[pos].fa;
					}
					length--;
				}
			} else {
				while(pos&&!a[pos].ch[s[i]-'a'])pos=a[pos].fa;
				if(pos==0) {
					pos=1;
					length=0;
				} else {
					length=a[pos].len+1;
					pos=a[pos].ch[s[i]-'a'];
				}
			}
		}
		cout<<res<<endl;
	}
}

P1117 [NOI2016] 优秀的拆分

如果一个串是AABB的形式、他就是优秀的拆分,求优秀拆分个数

我们考虑f[i]是以i为结尾的AA串,g[i]是以i为开头的aa串
那么结果就是f[i]*g[i+1]的总和
考虑枚举长度len、每隔len设一个点,我们发现如果相邻两个点的LCS和LCP大于len那么它们之间就有贡献、考虑差分维护
LCS与LCP利用SAM查询LCA维护、即可统计答案

#include <bits/stdc++.h>
const int maxn=6e4+50;
using namespace std;
struct SAM {
	int head[maxn],nex[maxn],to[maxn],ecnt;
	int son[maxn];
	int top[maxn];
	int pos[maxn];
	int fa[maxn];
	int size[maxn];
	int dep[maxn<<1];
	struct node {
		int ch[26];
		int len,fa;
	} a[maxn];
	int pre=1,tot=1;
	inline void add(int x,int y) {
		to[++ecnt]=y;
		nex[ecnt]=head[x];
		head[x]=ecnt;
	}
	inline void insert(int c,int k) {
		int p=pre,np=pre=++tot;
		a[np].len=a[p].len+1;
		pos[k]=np;
		for(; p&&!a[p].ch[c]; p=a[p].fa) a[p].ch[c]=np;
		if(!p) a[np].fa=1;
		else {
			int q=a[p].ch[c];
			if(a[q].len==a[p].len+1) a[np].fa=q;
			else {
				int nq=++tot;
				a[nq]=a[q];
				a[nq].len=a[p].len+1;
				a[q].fa=a[np].fa=nq;
				for(; p&&a[p].ch[c]==q; p=a[p].fa) a[p].ch[c]=nq;
			}
		}
	}
	inline void edge() {
		for(int i=2; i<=tot; i++) {
			add(a[i].fa,i);
		}
		dfs(1,1);
		dfs_list(1,1);
	}

	inline void dfs(int x,int f) {
		dep[x]=dep[f]+1;
		fa[x]=f;
		size[x]=1;
		int maxson=-1;
		for(int i=head[x]; i; i=nex[i]) {
			int y=to[i];
			if(y==f) continue;
			dfs(y,x);
			size[x]+=size[y];
			if(size[y]>maxson)son[x]=y,maxson=size[y];

		}
	}

	inline void dfs_list(int x,int topf) {
		top[x]=topf;
		if(!son[x])return;
		dfs_list(son[x],topf);
		for(int i=head[x]; i; i=nex[i]) {
			int y=to[i];
			if(y==fa[x]||y==son[x])continue;
			dfs_list(y,y);
		}
	}
	inline int LCA(int u,int v) {
		u=pos[u],v=pos[v];
		for(; top[u]^top[v]; u=fa[top[u]])
			if(dep[top[u]]<dep[top[v]]) swap(u,v);
		return a[dep[u]<dep[v]?u:v].len;
	}
} SA1,SA2,zero;
int chf1[maxn];
int chf2[maxn];
char s[maxn];
int main() {
//	freopen("P1117_13.in.txt","r",stdin);
	int T;
	scanf("%d",&T);
	while(T--) {
		scanf("%s",s);
		SA1=zero;
		SA2=zero;
		int l=strlen(s);
		for(int i=0; i<l; i++)SA1.insert(s[i]-'a',i+1);
		for(int i=l-1; i>=0; i--)SA2.insert(s[i]-'a',i+1);
		SA1.edge();
		SA2.edge();
		for (int len = 1 ; len <= l/2 ; len ++ ) {
			for (int i = len ; i + len - 1 <= l ; i += len ) {
				int j = i + len , hm = 0 , qm = 0 ;
				if(j<=l) hm=min(SA2.LCA(i,j),len);
				if(i>1) qm=min(SA1.LCA(i-1,j-1),len-1);
				if(qm+hm<len) continue;
				--chf1[j+hm],++chf1[j+hm-1-(qm+hm-len)],
				++chf2[i-qm],--chf2[i-qm+1+(qm+hm-len)];
			}
		}
		for (int i = 1 ; i <= l ; i++ ) chf1[i] += chf1[i-1] , chf2[i] += chf2[i-1] ;
		long long ans = 0 ;
		for (int i = 2 ; i <= l ; i++ ) ans += 1ll * chf1[i-1] * chf2[i] ;
		for(int i=0; i<=l+100; i++)chf1[i]=chf2[i]=0;
		printf("%lld\n",ans);
	}
}