题解 CF580E 【Kefa and Watch】

线段树+Hash

听说这方法比不过暴力?(雾)

解题过程

首先考虑第二个问题,如何快速求解一个子串是不是另一个串的循环节。

此处有一个结论:
假设 S 为 的长度为 n 。
只要判断 S[1…n-d+1] 和 S[d+1…n] 是否相等即可。
简单的想,假设当前循环节是\(k\),那么就有:
\(a\)
\(a*ch+b\)
\(a*ch^2+b*ch+c\)
\(a*ch^3+b*ch^2+c*ch+d\)
\(a*ch^4+b*ch^3+c*ch^2+d*ch+e\)
\(a*ch^5+b*ch^4+c*ch^3+d*ch^2+e*ch+f\)
必从\(k\)个字母循环,假设\(k=2\).那么就有:
\(a\)
\(a*ch+b\)
\(a*ch^2+b*ch+a\)
\(a*ch^3+b*ch^2+a*ch+b\)
\(a*ch^4+b*ch^3+a*ch^2+b*ch+a\)
\(a*ch^5+b*ch^4+a*ch^3+b*ch^2+a*ch+b\)
发现前面两个和后面两个Hash值相等,再通过数学归纳法,可以证明。
其实也很好想,因为我们要通过Hash[n]-Hash[n-d]相减来判断是不是s[0]-s[d]的多少\(ch\)倍,必须满足后面几个相等,因为只有是循环节,才能将前面的抵消掉。

第一个问题,如何处理赋值与Hash值之间的关系

一段的一段的赋值,查找也是一段一段的,很容易可以想到用线段树来维护一个Hash值。
每个点存当前的Hash值,但是这里就有一个问题,因为是存的从\(ch^0\)开始的,那么怎么才能转移到父亲节点呢?

kano[x].h=((kano[x<<1].h*Pow[kano[x<<1|1].rkano[x<<1|1].l+1]+kano[x<<1|1].h)%mod+mod)%mod;

左儿子里的值和他在父亲节点应当是的值差了什么呢?
根据Hash函数的定义:\(Hash(l,r)=Hash[r]-Hash[l-1]*p^{r-l+1}\)
所以说,左儿子里的值就差了乘上一个\(ch^{sizeof(rightson)}\)
这个问题解决了,那\(pushdown\)又该如何呢?

void pushdown(int x){
	if(kano[x].lazy+1){
		kano[x<<1].lazy=kano[x<<1|1].lazy=kano[x].lazy;
		kano[x<<1].h=Pow2[kano[x<<1].r-kano[x<<1].l]*kano[x].lazy%mod;
		kano[x<<1|1].h=Pow2[kano[x<<1|1].r-kano[x<<1|1].l]*kano[x].lazy%mod;
		kano[x].lazy=-1;
	}
	return ;
}

\(lazy\)里存的是将要变成的值,那么如何将这些值赋到儿子中呢?
可以看,在没有赋值前假设左儿子的值是:
\(x*ch^3+y*ch^2+z*ch\)
那么赋值之后就应该是:\(k*ch^3+k*ch^2+k*ch\).
可以发现,就是在\(k\)的基础上\(*\)了一个\(ch^3+ch^2+ch\),因此就可以同时维护一个\(ch^x+xh^{x-1}+ch^{x-2}+……ch\)的一个东西,就可以快速地\(pushdown\)

注意事项

1.有递归操作时,一定都下传\(lazy\)
2.注意边界。
3.我每次修改时都将k加了1,防止0,\(lazy\)为-1,防止0的出现。

参考代码

#include<bits/stdc++.h>
using namespace std;
const int b=17;
const int N=1e5+50;
const int mod=1e9+9;
typedef unsigned long long ULL;
char s[N];
int n,m,opt,k,l,r;
ULL Pow[N],Pow2[N];
struct Kano{
	int l,r,lazy;
	ULL h;
}kano[N*6];
void pushup(int x){
	kano[x].h=((kano[x<<1].h*Pow[kano[x<<1|1].r-kano[x<<1|1].l+1]+kano[x<<1|1].h)%mod+mod)%mod;
}
void pushdown(int x){
	if(kano[x].lazy+1){
		kano[x<<1].lazy=kano[x<<1|1].lazy=kano[x].lazy;
		kano[x<<1].h=Pow2[kano[x<<1].r-kano[x<<1].l]*kano[x].lazy%mod;
		kano[x<<1|1].h=Pow2[kano[x<<1|1].r-kano[x<<1|1].l]*kano[x].lazy%mod;
		kano[x].lazy=-1;
	}
	return ;
}
void build(int x,int l,int r){
	kano[x].l=l;kano[x].r=r;kano[x].lazy=-1;
	if(l==r){
		kano[x].h=s[l]-'0'+1;
		return;
	}
	int mid=(kano[x].l+kano[x].r)>>1;
	build(x<<1,l,mid),build(x<<1|1,mid+1,r);
	pushup(x);
}
void add(int x,int l,int r,int a){
	if(kano[x].l==l&&kano[x].r==r){
		kano[x].lazy=a;
		kano[x].h=Pow2[kano[x].r-kano[x].l]*a%mod;
		return;
	}
	pushdown(x);
	int mid=(kano[x].l+kano[x].r)>>1;
	if(mid>=r) add(x<<1,l,r,a);
	else if(mid<l) add(x<<1|1,l,r,a);
	else add(x<<1,l,mid,a),add(x<<1|1,mid+1,r,a);
	pushup(x);
}
ULL query(int x,int l,int r){
	if(l>r) return 0;
	if(kano[x].l==l&&kano[x].r==r)
		return kano[x].h;
	pushdown(x);
	int mid=(kano[x].l+kano[x].r)>>1;
	if(mid>=r) return query(x<<1,l,r);
	else if(mid<l) return query(x<<1|1,l,r);
	else return (query(x<<1,l,mid)*Pow[r-mid]+query(x<<1|1,mid+1,r))%mod;
}
int main(){
	scanf("%d%d%d",&n,&m,&k);
	m+=k;
	scanf("%s",s+1);
	Pow[0]=Pow2[0]=1;
	for(int i=1;i<=n;i++) Pow[i]=Pow[i-1]*b%mod,Pow2[i]=(Pow2[i-1]*b+1)%mod;
	build(1,1,n);
	for(int i=1;i<=m;i++){
		scanf("%d%d%d%d",&opt,&l,&r,&k);
		if(opt==1) add(1,l,r,k+1);
		else printf("%s\n",query(1,l,r-k)==query(1,l+k,r)?"YES":"NO");
	}
}

听说这东西打起来很爽,调起来更爽.(雾)

posted @ 2019-12-03 09:38  贰冬  阅读(180)  评论(0编辑  收藏  举报