P5341 [TJOI2019]甲苯先生和大中锋的字符串(后缀自动机+差分数组维护)

给出一个字符串。

请你找出所有出现次数恰好为k次的子串中,出现次数最多的子串长度。

先建出后缀自动机。

每个节点所代表的子串长度区间为len[link[i]]+1到len[i]。

每个节点所代表的子串出现次数是它的link树子树大小,

用差分数组维护一下。

//一个子串的出现次数=
//它的link树子树大小
//先找到link树子树大小恰好为k的节点
//然后这个节点,管理的节点长度区间是len[i]~len[link[i]]-1
//那么可以用差分数组维护每种长度的出现次数
//然后就是答案?
#include<bits/stdc++.h>
using namespace std;
const int maxn=3e5+100;
int len[maxn],link[maxn],nxt[maxn][26],tot,lst,sz[maxn];
vector<int> g[maxn];
void clear () {
	for (int i=0;i<=tot;i++) {
		len[i]=link[i]=sz[i]=0;
		g[i].clear();
		for (int j=0;j<26;j++) nxt[i][j]=0;
	}
	tot=1;
	lst=1;
}
string s;
int k;
void sam_extend (char c) {
	int cur=++tot;
	len[cur]=len[lst]+1;
	sz[cur]=1;
	int p=lst;
	while (p&&!nxt[p][c-'a']) {
		nxt[p][c-'a']=cur;
		p=link[p];
	}
	if (!p) {
		link[cur]=1;
	}
	else {
		int q=nxt[p][c-'a'];
		if (len[p]+1==len[q]) {
			link[cur]=q;
		}
		else {
			int clone=++tot;
			len[clone]=len[p]+1;
			for (int i=0;i<26;i++) {
				nxt[clone][i]=nxt[q][i];
			}
			link[clone]=link[q];
			while (p&&nxt[p][c-'a']==q) {
				nxt[p][c-'a']=clone;
				p=link[p];
			}
			link[q]=link[cur]=clone;
		}
	}
	lst=cur;
	sz[cur]=1;
}
int _;
void dfs (int u) {
	for (int v:g[u]) {
		dfs(v);
		sz[u]+=sz[v];
	}
}
int d[maxn];
int main ()  {
	cin>>_;
	while (_--) {
		cin>>s>>k;
		clear();
		for (char i:s) sam_extend(i);
		for (int i=2;i<=tot;i++) g[link[i]].push_back(i);
		dfs(1);
		for (int i=0;i<=s.size()+1;i++) d[i]=0;
		for (int i=1;i<=tot;i++) {
			if (sz[i]==k) {
				d[len[i]+1]--;
				d[len[link[i]]+1]++;
			}
		}
		int Max=0;
		int ans=-1;
		int sum=0;
		for (int i=1;i<=s.size();i++) {
			sum+=d[i];
			Max=max(Max,sum);
		}
		if (Max==0) {
			printf("-1
");
			continue;
		}
		sum=0;
		for (int i=1;i<=s.size();i++) {
			sum+=d[i];
			if (sum==Max) ans=i;
		}
		printf("%d
",ans);
	}
}
原文地址:https://www.cnblogs.com/zhanglichen/p/15016123.html