[FJWC2020]黑红兔

黑红兔

题意

在这里插入图片描述

题解

这暴力比正解还快3倍。

看到这道题,应该是很容易想到dp的。
d p i , j dp_{i,j} dpi,j表示选取区间 i , j i,j i,j时的最大段数,这样dp,明显是 O ( n 4 ) O\left(n^4\right) O(n4),只有 10 p t s 10pts 10pts
我们明显是可以对上面的dp进行优化的。
很明显,一定存在一种选法只比上面一段多 1 1 1,所以总的段数长度一定不会超过 2 n \sqrt{2n} 2n ,可以将 d p dp dp的第二维换成该段长度,也就被压缩到了 O ( n 3 ) O\left(n^3\right) O(n3)
而如果像上面这样的话, d p i , j dp_{i,j} dpi,j是一定等于 j j j的,于是第 2 2 2维就可以被去掉。我们直接对第一维进行枚举即可,有方程式 d p i = max ⁡ i + 1 n ( min ⁡ ( min ⁡ ( d p j , l c p ( i , j ) ) , j − i ) + 1 ) dp_{i}=\max_{i+1}^{n}(\min(\min(dp_{j},lcp(i,j)),j-i)+1) dpi=maxi+1n(min(min(dpj,lcp(i,j)),ji)+1)
于是,我们可以在枚举 i , j i,j i,j的时候,对这两者的后面的 l c p ( i , j ) lcp(i,j) lcp(i,j)值进行二分,找出它们的最长 l c p lcp lcp,比对两个串是否相等可以采用哈希。

有了上面的方法,我们就可以对我们的统计方法进行更深一步优化了。
如果我们的哈希模数开得足够小,我们就可以直接记录下出现了那些串了。
我们可以直接对看 i i i m i d mid mid长度的后缀是否出现过,这样就不需要枚举 j j j了。
时间复杂度 O ( n n l o g   n ) O\left(n\sqrt{n}log\,n\right) O(nn logn)

但当串够长是可能会出现判错的情况,所以我们要尽量减少我们枚举的串数量。
很容易发现有结论 f i ⩽ f i + 1 + 1 f_{i}\leqslant f_{i+1}+1 fifi+1+1,所以我们可以缩小我们的二分上界,使之判断数变少。
其实这样就可以过了,但是这明显不是正解嘛。

但事实上我们是不需要对 f f f进行二分的,上面 f f f的形式与 k m p kmp kmp的形式很像,可以用势能的形式考虑,我们真正枚举的 f f f次数总和是不会超过 n n n的。
所以,我们可以先采取后缀数组,使所有的后缀排序后,找到 l c p ( i , j ) ⩽ m i d lcp(i,j)\leqslant mid lcp(i,j)mid的那一段,这个过程可以用st表rmq和二分来进行维护,找到区间,即可能贡献我们 f f f j j j必须 r k rk rk在这个范围内。
之后再看这个区间中有没有点的dp值大于等于 m i d mid mid,这个可以通过主席树来进行维护。
主席树下标为 r k rk rk,树上维护已有后缀 r k rk rk在该节点区间内的 d p dp dp最大值。
如果我们枚举 f f f所对应的后缀区间内有 d p j ⩾ f − 1 dp_{j}\geqslant f-1 dpjf1的点,那么就能够贡献到我们的 d p i dp_{i} dpi
由于我们的 j j j要求 j ⩾ i + f i j\geqslant i+f_{i} ji+fi,我们直接在 i + f i i+f_{i} i+fi的主席树上查询我们 l c p ( i , j ) ⩾ f i − 1 lcp(i,j)\geqslant f_i-1 lcp(i,j)fi1区间的最大值就行了。
说不定直接指针也行。

时间复杂度 O ( n l o g   n ) O\left(nlog\,n\right) O(nlogn),但实测跑得没有哈希快

源码

正解

#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
#include<unordered_map>
using namespace std;
const int mo=1e8+7;
const int jzm=233;
const int INF=0x7f7f7f7f;
#define MAXN 500005
typedef long long LL;
template<typename _T>
void read(_T &x){
	_T f=1;x=0;char s=getchar();
	while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
	x*=f;
}
char str[MAXN];
int n,m,ans,dp[MAXN],sa[MAXN],rk[MAXN],hei[MAXN],st[25][MAXN];
int x[MAXN],y[MAXN],c[MAXN],z[MAXN],lg[MAXN],root[MAXN];
struct ming{int lson,rson,maxx;};
int add(const int x,const int y){return x+y<mo?x+y:x+y-mo;}
int qkpow(int a,int s){int t=1;while(s){if(s&1)t=1ll*a*t%mo;a=1ll*a*a%mo;s>>=1;}return t;}
void getSa(){
	for(int i=1;i<=n;i++)c[x[i]=str[i]]++;
	for(int i=2;i<=m;i++)c[i]+=c[i-1];
	for(int i=n;i>0;i--)sa[c[x[i]]--]=i;
	for(int k=1;k<=n;k<<=1){
		int num=0;for(int i=n-k+1;i<=n;i++)y[++num]=i;
		for(int i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k;
		for(int i=1;i<=m;i++)c[i]=0;
		for(int i=1;i<=n;i++)c[x[i]]++,z[i]=x[i];
		for(int i=2;i<=m;i++)c[i]+=c[i-1];
		for(int i=n;i>0;i--)sa[c[x[y[i]]]--]=y[i];
		for(int i=1;i<=n;i++)x[i]=y[i]=0;x[sa[1]]=1;num=1;
		for(int i=2;i<=n;i++)x[sa[i]]=(z[sa[i]]==z[sa[i-1]]&&z[sa[i]+k]==z[sa[i-1]+k])?num:++num;
		if(num==n)break;m=num;
	}
}
void getHi(){
	int k=0;for(int i=1;i<=n;i++)rk[sa[i]]=i;
	for(int i=1;i<=n;i++){
		if(k)k--;int j=sa[rk[i]-1];
		while(str[i+k]==str[j+k])k++;hei[rk[i]]=k;
	}
}
void getSt(){
	for(int j=2;j<=n;j++)lg[j]=lg[j>>1]+1;
	for(int i=0;i<n;i++)st[0][i]=hei[i+1];
	for(int i=1;i<=lg[n];i++)
		for(int j=0;j<n-(1<<i-1);j++)
			st[i][j]=min(st[i-1][j],st[i-1][j+(1<<i-1)]);
}
int rmq(int l,int r){if(l==r)return INF;return min(st[lg[r-l]][l],st[lg[r-l]][r-(1<<lg[r-l])]);}
struct SegmentTree{
	private:
		int tot;ming tr[MAXN*30];
	public:
		void insert(int &now,int las,int l,int r,int ai,int aw){
			now=++tot;tr[now]=tr[las];tr[now].maxx=max(tr[now].maxx,aw);
			if(l==r)return ;int mid=l+r>>1;
			if(ai<=mid)insert(tr[now].lson,tr[las].lson,l,mid,ai,aw);
			if(ai>mid)insert(tr[now].rson,tr[las].rson,mid+1,r,ai,aw);
		}
		int query(int rt,int l,int r,int al,int ar){
			if(l>r||r<al||l>ar||!rt)return 0;int mid=l+r>>1,res=0;
			if(al<=l&&r<=ar)return tr[rt].maxx;
			if(al<=mid)res=max(res,query(tr[rt].lson,l,mid,al,ar));
			if(ar>mid)res=max(res,query(tr[rt].rson,mid+1,r,al,ar));
			return res;
		}
}T;
bool check(int pos,int len,int R){
	int l=rk[pos],r=n,al=l,ar=l;while(l<r){int mid=l+r+1>>1;if(rmq(rk[pos],mid)<len)r=mid-1;else l=mid;}
	ar=l;l=1;r=rk[pos];while(l<r){int mid=l+r>>1;if(rmq(mid,rk[pos])<len)l=mid+1;else r=mid;}al=l;
	return T.query(root[R],1,n,al,ar)>=len;
}
int main(){
	freopen("brr.in","r",stdin);
	freopen("brr.out","w",stdout);
	scanf("%s",str+1);n=(int)strlen(str+1);m=126;getSa();getHi();getSt();
	for(int i=n;i>0;i--){
		dp[i]=1;
		for(int j=min(n-i+1,dp[i+1]);j>=dp[i];j--)if(check(i,j,i+j+1)){dp[i]=j+1;break;}
		for(int j=min(n-i,dp[i+2]);j>=dp[i];j--)if(check(i+1,j,i+j+1)){dp[i]=j+1;break;}
		T.insert(root[i],root[i+1],1,n,rk[i],dp[i]);
	}
	for(int i=1;i<=n;i++)ans=max(ans,dp[i]);
	printf("%d\n",ans);
	return 0;
}

哈希

#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
#include<unordered_map>
using namespace std;
const int mo=1e8+7;
const int jzm=233;
#define MAXN 500005
typedef long long LL;
template<typename _T>
void read(_T &x){
	_T f=1;x=0;char s=getchar();
	while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
	x*=f;
}
bool mh[mo+5];char str[MAXN];
int n,dp[MAXN],lim,hs[MAXN],ph[MAXN],phi[MAXN],ans,maxx;
int add(const int x,const int y){return x+y<mo?x+y:x+y-mo;}
int qkpow(int a,int s){int t=1;while(s){if(s&1)t=1ll*a*t%mo;a=1ll*a*a%mo;s>>=1;}return t;}
int Hash(const int l,const int r){return 1ll*add(hs[r],mo-hs[l-1])*phi[l-1]%mo;}
int main(){
	freopen("brr.in","r",stdin);
	freopen("brr.out","w",stdout);
	scanf("%s",str+1);n=(int)strlen(str+1);ph[0]=phi[0]=1;const int inv=qkpow(jzm,mo-2);
	for(int i=1;i<=n;i++)ph[i]=1ll*ph[i-1]*jzm%mo,phi[i]=1ll*phi[i-1]*inv%mo;
	for(int i=1;i<=n;i++)hs[i]=add(hs[i-1],1ll*ph[i-1]*(str[i]-96)%mo);maxx=1;
	int now=n+1,las=n+1;
	for(int i=n;i>0;i--){
		dp[i]=1;int l=0,r=min(n-i+1,min(maxx,dp[i+1]+1));
		while(l<r){int mid=l+r+1>>1;if(mh[Hash(i,i+mid-1)])l=mid;else r=mid-1;}
		dp[i]=max(dp[i],l+1);l=0,r=min(n-i+1,min(maxx,dp[i+1]+1));
		while(l<r){int mid=l+r+1>>1;if(mh[Hash(i+1,i+mid)])l=mid;else r=mid-1;}
		dp[i]=max(dp[i],l+1);now=i+dp[i];maxx=max(maxx,dp[i]);
		for(int j=now;j<las;j++)for(int k=j;k<j+dp[j];k++)mh[Hash(j,k)]=1;las=now;
	}
	for(int i=1;i<=n;i++)ans=max(ans,dp[i]);
	printf("%d\n",ans);
	return 0;
}

谢谢!!!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值