[CF700E][JZOJ5558][线段树合并]Cool Slogan

###题目大意
给你一个长度为n的字符串S,求最长的一个字符串序列a[1…k]满足序列中的每一个字符串都是S的子串,且对于任意的 1 &lt; i &lt; = k 1&lt;i&lt;=k 1<i<=k都有a[i−1]在a[i]中至少出现两次。两次出现允许重叠。
问最大满足条件的k是多少。
n<=200000
部分分n<=4000
###解题思路
部分分可以很显然地设f[i,j]表示a[1]=s[i…j]时的最大k值。我们转移的时候,可以只转移s[i…j]的最长border,然后再加多两个转移,即转移到f[i-1,j]和f[i,j+1]。
观察性质,其实,后面两个转移是多余的。
也就是说,我们只要找到一个子串,他border调用次数最大。
考虑上SAM,在parent树上做,设F[x]表示节点x所代表的最长子串的k值。
显然x只能由parent链上的祖先转移过来,由于不能确定是哪个子串,我们不管border,直接用题意的出现次数即可。具体的,我们选择x的right集里面一个位置p,假如一个祖先y能转移到x,那么y的right集里面必须要有元素属于[p-mx_len[x]+mn_len[y],p-1],mnlen代表一个节点所代表的最短子串。转移过来的话,f值+1
我们可以发现,只需要从祖先中找f值最大的转移即可。我们设pos[x]来维护x到祖先,最大的f节点是谁。
right集判断,用可持久化线段树合并即可,常见的维护right集套路。
注意一定要每次合并都建新点。
###代码

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<map>
using namespace std;
typedef long long ll;
typedef double db;
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
#define cmax(a,b) (a=(a>b)?a:b)
#define cmin(a,b) (a=(a<b)?a:b)
const int N=4e5+5,mo=998244353,xs=1331;
const int M=N*20;
int ts,trie[N][26],f[N],fail[N],mx[N],n,lst[N],i,x,ans,arb[N],ls[M],rs[M],tt,rt[N],d[N],pos[N],le,v;
char s[N];
void change(int &x,int l,int r,int p)
{
	if (!x) x=++tt;
	if (l==r) return ;
	int m=l+r>>1;
	if (p<=m) change(ls[x],l,m,p);
	else change(rs[x],m+1,r,p);
}
int merge(int x,int ax)
{
	if (!x||!ax) return x+ax;
	int nx=++tt;
	ls[nx]=merge(ls[x],ls[ax]);
	rs[nx]=merge(rs[x],rs[ax]);
	return nx;
}
int get(int x,int l,int r,int i,int j)
{
	if (!x) return 0;
	if (l==i&&r==j)
		return 1;
	int m=l+r>>1;
	if (j<=m) return get(ls[x],l,m,i,j);
	else if (m<i) return get(rs[x],m+1,r,i,j);
	else return get(ls[x],l,m,i,m)+get(rs[x],m+1,r,m+1,j);
}
bool cmp(int x,int y)
{
	return mx[x]<mx[y];
}
int ins(int lst,int x)
{
	int p,q,np,nq,j;
	np=++ts;
	mx[np]=mx[lst]+1;
	p=lst;
	while (p&&!trie[p][x]) trie[p][x]=np,p=fail[p];
	if (!p) fail[np]=1;
	else
	{
		q=trie[p][x];
		if (mx[q]==mx[p]+1) fail[np]=q;
		else
		{
			nq=++ts;
			mx[nq]=mx[p]+1;
			while (p&&trie[p][x]==q) trie[p][x]=nq,p=fail[p];
			fail[nq]=fail[q];
			fail[q]=fail[np]=nq;
			fo(j,0,25) trie[nq][j]=trie[q][j];
		}
	}
	change(rt[np],1,n,i);
	arb[np]=i;
	return np;
}
int main()
{
	freopen("t2.in","r",stdin);
//	freopen("cat.out","w",stdout);
	scanf("%s",s+1);
	n=strlen(s+1);
	lst[0]=++ts;
	rt[1]=++tt;
	fo(i,1,n)
		lst[i]=ins(lst[i-1],s[i]-'a');
	fo(i,1,ts) d[i]=i;
	sort(d+1,d+1+ts,cmp);
	fd(i,ts,2)
	{
		x=d[i];
		rt[fail[x]]=merge(rt[fail[x]],rt[x]);
		cmax(arb[fail[x]],arb[x]);
	}
	pos[1]=1;
	mx[0]=-1;
	fo(i,2,ts)
	{
		x=d[i];
		pos[x]=pos[fail[x]];
		le=arb[x]-mx[x]+mx[fail[pos[x]]]+1;
		v=get(rt[pos[x]],1,n,le,arb[x]-1);
		if (v) 
		{
			f[x]=f[pos[x]]+1;
			pos[x]=x;
			cmax(ans,f[x]);
		}
	}
	printf("%d\n",ans);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值