zoukankan      html  css  js  c++  java
  • 「CF1063F」String Journey 题解

    首先随便就能看出来那个样例是来骗人的。我们要答案最优,显然 \(|t_k|+1=|t_{k-1}|\)

    证明:如果 \(|t_{k-1}|\neq |t_k|+1\),那么根据 \(\operatorname{Journey}\) 的定义,\(|t_{k-1}| >|t_k|+1\)。这样可能会占用 \(|t_{k-2}|\) 的某一个字符,显然不优。

    定义 \(dp_i\) 为以 \(i\) 为后缀的起步,最长的 \(\operatorname{Journey}\) 的长度。枚举转移点 \(j\),显然如果 \(j\) 合法,令 \(k=\min(j-i,dp_j+1)\)\(dp_i = k\)。根据 \(\operatorname{Journey}\) 的定义,应该满足:

    \[S[i \dots i+k-2]=S[j\dots j+k-2] \ ∨\ S[i+1 \dots i+k-1]=S[j\dots j+k-2] \]

    这个过程可以使用 hash 完成。此时我们已经得到了一个很搞笑的算法了。

    哦不可是这个毒瘤出题人要求我们用更高效的算法去做才能够对得起这个 \(3200\) 的评分。分析一下性质,通过打表我们也能得到:

    \[dp_i \leq dp_{i+1}+1 \]

    画图可以理解:\(dp_i\) 最终指向的位置一定不会超过 \(dp_{i+1}\) 指向的位置。

    这个意思是什么呢?我们定义一个指针 \(p\),表示最远能够转移的边界。根据上面的性质可以得到 \(p\) 单调不增

    看似麻烦了,因为现在我们要转移 dp 必须要满足两个条件:

    • \(S[i \dots p-1]=S[j\dots j+p-i-1] \ ∨\ S[i+1 \dots p]=S[j \dots j+p-i-1]\)
    • \(dp_j \geq dp_i+1\)

    满足这两个条件即可转移。如果无法达到就 \(p ← p-1\),再次进行判断。

    道理我都懂,如何判断呢?考虑用后缀数组维护,求出 height 数组,根据定义可以用二分在这个数组上面找到一个满足这个区间内所有元素 \(k\)\(i\)\(\operatorname{LCP}\) 长度都大于等于 \(p-i\) 的区间。现在我们要求是否 \(\exists j>p:dp_j \geq p-i\)。这个时候我们可以用线段树去维护这个最大值。每次挪动指针 \(p\) 的时候就将 \(dp\) 值加入,继续下一轮判断。

    #include<bits/stdc++.h>
    #define lc(x) (x<<1)
    #define rc(x) ((x<<1)|1)
    #define Mm int mid=(l+r)>>1
    using namespace std;
    char a[500005];
    int sa[500005],height[500005],cnt[500005],x[500005],tmp1[500005],Rank[500005],st[500005][21],lg[500005],maxn[2000005],dp[500005],n,m;
    void MakeSuffixArray()
    {
    	m=127;
    	for(int i=1;i<=n;++i)	++cnt[x[i]=a[i]];
    	for(int i=2;i<=m;++i)	cnt[i]+=cnt[i-1];
    	for(int i=n;i;--i)	sa[cnt[x[i]]--]=i;
    	for(int k=1;k<=n;k<<=1)
    	{
    		int tmp=0;
    		for(int i=n-k+1;i<=n;++i)	tmp1[++tmp]=i;
    		for(int i=1;i<=n;++i)	if(sa[i]>k)	tmp1[++tmp]=sa[i]-k;
    		for(int i=1;i<=m;++i)	cnt[i]=0;
    		for(int i=1;i<=n;++i)	++cnt[x[i]];
    		for(int i=2;i<=m;++i)	cnt[i]+=cnt[i-1];
    		for(int i=n;i;--i)	sa[cnt[x[tmp1[i]]]--]=tmp1[i],tmp1[i]=0;
    		swap(x,tmp1);
    		tmp=1;
    		x[sa[1]]=1;
    		for(int i=2;i<=n;++i)	x[sa[i]]=(tmp1[sa[i]]==tmp1[sa[i-1]] && tmp1[sa[i]+k]==tmp1[sa[i-1]+k])?tmp:++tmp;
    		if(tmp==n)	break;
    		m=tmp; 
    	}
    }
    void MakeHeightArray()
    {
    	int k=0;
    	for(int i=1;i<=n;++i)	Rank[sa[i]]=i;
    	for(int i=1;i<=n;++i)
    	{
    		if(Rank[i]==1)	continue;
    		if(k)	--k;
    		int j=sa[Rank[i]-1];
    		while(j+k<=n && i+k<=n && a[i+k]==a[j+k])	++k;
    		height[Rank[i]]=k;
    	}
    }
    int query_st(int l,int r)
    {
    	if(l>r)	return n;
    	int lgs=lg[r-l+1];
    	return min(st[l][lgs],st[r-(1<<lgs)+1][lgs]);
    }
    int query_l(int p,int q)
    {
    	int l=1,r=p,ans=0;
    	while(l<=r)
    	{
    		int mid=(l+r)>>1;
    		if(query_st(mid+1,p)>=q)	ans=mid,r=mid-1;
    		else	l=mid+1;
    	}
    	return ans;
    }
    int query_r(int p,int q)
    {
    	int l=p,r=n,ans=0;
    	while(l<=r)
    	{
    		int mid=(l+r)>>1;
    		if(query_st(p+1,mid)>=q)	ans=mid,l=mid+1;
    		else	r=mid-1;
    	}
    	return ans;
    }
    void modify(int l,int r,int x,int now,int val)
    {
    	if(l==r)
    	{
    		maxn[now]=val;
    		return ;
    	}
    	Mm;
    	if(x<=mid)	modify(l,mid,x,lc(now),val);
    	else	modify(mid+1,r,x,rc(now),val);
    	maxn[now]=max(maxn[lc(now)],maxn[rc(now)]);
    }
    int query(int l,int r,int x,int y,int now)
    {
    	if(l>=x && r<=y)	return maxn[now];
    	Mm;
    	int ans=-10086001;
    	if(x<=mid)	ans=max(ans,query(l,mid,x,y,lc(now)));
    	if(mid+1<=y)	ans=max(ans,query(mid+1,r,x,y,rc(now)));
    	return ans;
    }
    bool check(int x,int y)
    {
    	int l=query_l(x,y),r=query_r(x,y);
    //	if(query(1,n,l,r,1)>=y)
    	return query(1,n,l,r,1)<y;
    }
    int main(){
    	scanf("%d",&n);
    	if(n==1)	return puts("1")&0;
    	scanf("%s",a+1);
    	MakeSuffixArray();
    	MakeHeightArray();
    	for(int i=1;i<=n;++i)	st[i][0]=height[i];
    	for(int j=1;j<=20;++j)	for(int i=1;i+(1<<j)-1<=n;++i)	st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
    	lg[1]=0;
    	for(int i=2;i<=n;++i)	lg[i]=lg[i>>1]+1;
    	dp[n]=1;
    	int pointer=n,ans=0;
    	for(int i=n-1;i;--i)
    	{
    		while(check(Rank[i],pointer-i) && check(Rank[i+1],pointer-i) && i<pointer)	--pointer,modify(1,n,Rank[pointer+1],1,dp[pointer+1]);
    		ans=max(ans,dp[i]=pointer-i+1);
    	}
    	printf("%d",ans);
    	return 0;
    }
    
  • 相关阅读:
    【阿里笔试2】给定一组只包含数字的字符串,请恢复到有效的非私有网段地址组合
    【阿里笔试1】 把一个数组分成四份,三个分割点不算进求和中,使得每份的和要相同。
    【转载】Java基础之String中equals,声明方式,等大总结
    8-网易人力资源部门面试
    登录页面简单模板
    微机原理笔记(2)------8255A芯片
    微机原理笔记(1)------8259A芯片
    数据库复习笔记(4)------关系数据
    数据库复习笔记(3)------E-R模型
    数据库复习笔记(2)------关系代数
  • 原文地址:https://www.cnblogs.com/amagaisite/p/13528921.html
Copyright © 2011-2022 走看看