【ZJOI2017】【BZOJ4877】【洛谷P5211】字符串(分块)(字符串哈希)(线段树)

11 篇文章 0 订阅
9 篇文章 0 订阅

洛谷传送门

BZOJ传送门


题解:

写ZJOI真是作死的绝佳方式

于是一个下午只写了一道题。

首先是毒瘤至极的性质分析。

对于字符串 S S S的某个后缀 v v v,我们称它是 S S S的好后缀当且仅当存在某个字符串 T T T,使得 v T vT vT能够成为 S T ST ST的最小后缀。

通过一波莫名其妙让人吐血的分析我们可以知道 S S S的好后缀最多只有 O ( log ⁡ ∣ S ∣ ) O(\log |S|) O(logS)个。

引理:若 a , b a,b a,b都是 S S S的好后缀,且 ∣ a ∣ > ∣ b ∣ |a|>|b| a>b,必有 ∣ a ∣ ≥ 2 ∣ b ∣ |a|\geq 2|b| a2b

考虑证明,假设 ∣ a ∣ &gt; ∣ b ∣ |a|&gt;|b| a>b,则 b b b必然为 a a a的前缀,若 ∣ a ∣ &lt; 2 ∣ b ∣ |a|&lt;2|b| a<2b,则我们知道, a a a有一个长度为 ∣ a ∣ − ∣ b ∣ |a|-|b| ab的周期,不妨设 a = c d c d c , b = c d c a=cdcdc,b=cdc a=cdcdcb=cdc,其中 c , d c,d c,d代表的都是不定字符串。

假设在加上字符串 t t t之后 b t bt bt成为了最小后缀,那么 a t &gt; b t at&gt;bt at>bt,即 c d c d c t &gt; c d c t cdcdct&gt;cdct cdcdct>cdct,注意到必然有 c d c t &gt; c t cdct&gt;ct cdct>ct,也就是 b t &gt; c t bt&gt;ct bt>ct,与前面假设 b t bt bt为最小后缀不符。

所以 ∣ S ∣ |S| S的最小后缀长度是倍增的,最多只有 O ( log ⁡ ∣ S ∣ ) O(\log |S|) O(logS)个。

我们考虑合并两个字符串 u , v u,v u,v,现在需要考虑这 O ( log ⁡ u + log ⁡ v ) O(\log u+\log v) O(logu+logv)个可能的最小后缀有哪些能够保留下来。直接拿所有位置作为新串的的后缀来比较就行了,如果比到后缀都不知道哪个更大的话,直接暴留更长的就行了。

现在还有一个问题:维护区间加和比较两个字符串大小,我们实际上还是可以二分 l c p lcp lcp然后比较哈希值,如果用线段树维护哈希值的话可能有点蛋疼,复杂度为 O ( n log ⁡ 3 n + m log ⁡ 4 n ) O(n\log^3 n+m\log^4 n) O(nlog3n+mlog4n)

所以我们用分块维护哈希值,保证哈希值询问能够在 O ( 1 ) O(1) O(1)时间内回答,区间加在 O ( n ) O(\sqrt n) O(n )时间内完成,复杂度为 O ( n log ⁡ 2 n + m log ⁡ 3 n + m n ) O(n\log^2 n+m\log^3 n+m\sqrt n) O(nlog2n+mlog3n+mn )


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define gc get_char
#define cs const

namespace IO{
	inline char get_char(){
		static cs int Rlen=1<<22|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}
	
	template<typename T>
	inline T get(){
		char c;bool f=0;
		while(!isdigit(c=gc()))f=c=='-';T num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return f?-num:num;
	}
	inline int getint(){return get<int>();}
}
using namespace IO;

using std::cerr;
using std::cout;
#define ull unsigned long long
cs int N=2e5+5;

int n,Q;

namespace HASH{
	cs int SqrtN=500;
	cs int base=1e9+7;
	int bsiz,tot,lp[SqrtN],rp[SqrtN];
	int blk[N],add[SqrtN],v[N];
	ull pre[N],tag[SqrtN],pw[N],pws[N];
	
	inline void init(){
		bsiz=sqrt(n);
		for(int re i=1;i<=n;++i){
			if((i-1)%bsiz==0){rp[tot]=i-1;lp[++tot]=i;}
			blk[i]=tot;
		}
		pw[0]=pws[0]=1;
		for(int re i=1;i<=n;++i){
			v[i]=getint()+5e8;
			pw[i]=pw[i-1]*base;
			pws[i]=pws[i-1]+pw[i];
			pre[i]=pre[i-1]+v[i]*pw[i];
		}
	}
	
	inline int val(int p){return v[p]+add[blk[p]];}
	
	inline ull prefix(int p){
		return pre[p]+tag[blk[p]]+add[blk[p]]*(pws[p]-pws[lp[blk[p]]-1]);
	}
	
	inline void modify(int l,int r,int d){
		if(blk[l]!=blk[r]){
			for(int re i=l,li=rp[blk[l]];i<=li;++i){
				v[i]+=d;
				pre[i]+=d*(pws[i]-pws[l-1]);
			}
			for(int re i=r,li=lp[blk[r]];i>=li;--i){
				v[i]+=d;
				pre[i]+=d*(pws[i]-pws[l-1]);
			}
			for(int re i=blk[l]+1,li=blk[r]-1;i<=li;++i){
				add[i]+=d;
				tag[i]+=d*(pws[lp[i]-1]-pws[l-1]);
			}
		}
		else {
			for(int re i=l;i<=r;++i){
				v[i]+=d;
				pre[i]+=d*(pws[i]-pws[l-1]);
			}
		}
		ull delta=d*(pws[r]-pws[l-1]);
		for(int re i=r+1,li=rp[blk[r]];i<=li;++i)pre[i]+=delta;
		for(int re i=blk[r]+1;i<=tot;++i)tag[i]+=delta;
	}
	
	inline bool cmp(int x,int y,int l){
		if(x>y)std::swap(x,y);
		return (prefix(x+l-1)-prefix(x-1))*pw[y-x]==prefix(y+l-1)-prefix(y-1);
	}
	
	inline int lcp(int x,int y){
		if(x>y)std::swap(x,y);
		int l=0,r=n-y+1,d=y-x;
		ull lx=prefix(--x);
		ull ly=prefix(--y);
		while(l<r){
			int mid=l+r+1>>1;
			((prefix(x+mid)-lx)*pw[d]==prefix(y+mid)-ly)?(l=mid):(r=mid-1);
		}
		return l;
	}
}

namespace SGT{
	struct atom{
		int l,r;
		std::vector<int> pos;
		friend atom operator+(cs atom &a,cs atom &b){
			assert(a.r==b.l-1);
			atom ans;ans.l=a.l,ans.r=b.r;
			std::vector<int> &p=ans.pos;
			cs std::vector<int> &ap=a.pos,&bp=b.pos;
			for(int re i=0,x;i<ap.size();++i){x=ap[i];
				bool flag=true;
				while(!p.empty()){
					int y=p.back();
					if(HASH::cmp(x,y,b.r-x+1))break;
					int lcp=HASH::lcp(x,y);
					if(HASH::val(x+lcp)>HASH::val(y+lcp)){flag=false;break;}
					else p.pop_back();
				}
				if(flag&&(p.empty()||b.r-x+1<=x-p.back()))p.push_back(x);
			}
			for(int re i=0,x;i<bp.size();++i){x=bp[i];
				bool flag=true;
				while(!p.empty()){
					int y=p.back();
					if(HASH::cmp(x,y,b.r-x+1))break;
					int lcp=HASH::lcp(x,y);
					if(HASH::val(x+lcp)>HASH::val(y+lcp)){flag=false;break;}
					else p.pop_back();
				}
				if(flag&&(p.empty()||b.r-x+1<=x-p.back()))p.push_back(x);
			}
			return ans;
		}
	};
	
	atom a[N<<2];
#define lc k<<1
#define rc k<<1|1
	
	inline void build(int k,int l,int r){
		if(l==r){
			a[k].l=l;
			a[k].r=r;
			a[k].pos.push_back(l);
			return ;
		}
		int mid=l+r>>1;
		build(lc,l,mid);build(rc,mid+1,r);
		a[k]=a[lc]+a[rc];
	}
	
	inline void modify(int k,int l,int r,int ql,int qr){
		if(ql<=l&&r<=qr)return ;
		int mid=l+r>>1;
		if(ql<=mid)modify(lc,l,mid,ql,qr);
		if(mid<qr)modify(rc,mid+1,r,ql,qr);
		a[k]=a[lc]+a[rc];
	}
	
	inline atom query(int k,int l,int r,int ql,int qr){
		if(ql<=l&&r<=qr)return a[k];
		int mid=l+r>>1;
		if(qr<=mid)return query(lc,l,mid,ql,qr);
		if(mid<ql)return query(rc,mid+1,r,ql,qr);
		return query(lc,l,mid,ql,qr)+query(rc,mid+1,r,ql,qr);
	}
	
	inline int query(int l,int r){
		return query(1,1,n,l,r).pos.back();
	}
}

signed main(){
//	freopen("string.in","r",stdin);
	n=getint();Q=getint();
	HASH::init();
	SGT::build(1,1,n);
	while(Q--)switch(getint()){
		case 1:{
			int l=getint(),r=getint(),d=getint();
			HASH::modify(l,r,d);
			SGT::modify(1,1,n,l,r);
			break;
		}
		case 2:{
			int l=getint(),r=getint();
			cout<<SGT::query(l,r)<<"\n";
			break;
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值