CF#321-DIV2-E. Kefa and Watch-线段树+字符串哈希

http://codeforces.com/contest/580/problem/E

题意

给你一段长度为n的字符串,给你m,k,表示有m+k次操作

格式 operation L R D

operation =1; 表示把第L到第R个字符改为d

operation =2;表示求 L到R这个字符串是否存在长度为d的循环,是输出YES,否则NO

n=10^5;  操作也是10^5

思路:

一开始看到区间修改,觉得可以是线段树...

然后要  查询循环节。。。想到kmp..O(N),也不行,必须得logn

但是还是用到了MP算法的一个应用,求循环节

http://blog.csdn.net/viphong/article/details/48498595  //此题是求循环节个数

由其中结论可知,  对于 一个长度为N的字符串,如果字符串【1,n-k】与【k,n】是相等的,那么说明k是该字符串的循环节长度。

从而,对操作二,查询【L,R】是否有d长度的循环节,只需要查询 【L,R-d】和【d,R】是否相等即可。此处相等显然不能用一个个字符去匹配(超时),因此我们可以用字符串hash的方法, 如果两个字符串经过hash函数计算得到的 hash值一样,我们就可以很大概率确定他们是同一个字符串了  (为提高正确率,取了2次hash,AC之后只取一个hash也能ac)

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

关于字符串hash的 解释: 

哈希本质上是一种映射 好的hash需要避免冲突

而字符串哈希,就是希望把一个字符串经过hash函数计算后,转化成一个数字。

最好的结果,就是希望任一个字符串,经过hash函数计算后,都转化成一个不同的数字(不可能完全做到,但是好的hash函数可以很大概率做到)

本题我们需要判定2个字符串是否相等,那么只需要分别转成2个数字,比较是否相等,相等则近似认为字符串相等,反之认为不等

因此hash函数需要满足

1【不同字符串得到的哈希值尽可能不等】

2【相等字符串得到的哈希值一定相等】


例如字符串abc 。我们选择7作为基底

得到一个ans= a*7^2+b*7^1+c*7^0 这个ans就是abc字符串得到的哈希值,

对应的hash函数为:

int hash(string i)
{
 int l=i.length, s=7,ans=0,t=1e9+7;
 for (int q=0;q<l;q++)
   {
     ans+=i[q]*s;
     s*=7;
     ans=ans%t;
   }
return ans;
此处取mod为1000000007;

只要字符串一样,经过hash得到的ans必然一样,如果字符串不一样,得到的hash是不一样的(理想的情况下,当然本处选7为基底是很大几率出现冲突的)

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

知道怎么计算字符串的哈希值后,显然我们不能每次对字符串求一次hash,我们需要在线段树里维护字符串的hash值

用得到的数有

int    add[4*N] ;//延迟标记
__int64 prime[4*N]; //存hash基底的i次方
__int64 sum[4*N]; ///存hash基底的前i次方和(即系数全为1)(为操作1服务)
__int64 hash[4*N];  //存当前节点管辖字符串的 hash值
__int64 mod;
__int64 fact; //hash的基底 一个大质数 

先预处理好质数fact的i次方,存到prime[]

然后sum存的是  hash基底的前i次方和 //每一项系数为1

例如sum[7]= fact^7+fact^6+fact^5+fact^4+fact^3+fact^2+fact^1;

***************

好,接下来我们讲怎么维护字符串的hash值:

*********************************************

【从两段儿子区间的hash值求到一段父区间的hash值】:

一开始递归建树的时候,最底层 l==r这种长度为1的区间时,显然是单个字符,其hash值为自身字符acsii码

现在假设先得到区间【1,1】的hash值为hash[1]、再得到【2,2】的hash值为hash[2]

那么我们要求【1,2】的hash值的时候,  

区间【1,1】记为A,区间【2,2】记为B,区间【1,2】记为C

需要由左子区间与右子区间的字符串合并计算......以左边为最低位的话 

hash[C]= hash[A]+ hash[B]* prime[len(A)]   //  len(A)是A字符串的长度,prime【len】是该长度对应的hash基底的i次方

那么现在我们就得到 从两段儿子区间的hash值求到一段父区间的hash值了

**********************************************

set操作改变一段字符串的hash值】:

假如我们要set一段区间【L,R】的值为 d

除了做延迟标记,我们利用之前的sum[]、直接维护该区间hash值

因为sum[]存的是 系数为1的 hash基底的前i次方和

那么,如果这段区间被set为d,显然 该段字符串的hash值为 sum[len-1]*d   //len为区间长度

用上面的维护方法维护涉及到的区间就好了 

**********************************************

基本用到的操作就是上面两个了,剩下的就是一些细节问题了。。。

例如 对字符串计算hash时,要用字符串的ascii码来计算,不要用阿拉伯数字来计算

因为001和01如果用阿拉伯数计算是用到0 0 1和 0 1 ,其hash值会一样。。。而用ascii就不会出现这样的情况

细节看代码吧。。。233


#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
#include <iostream>
#include <queue>
#include <map>
#include <set>
#include <vector>
using namespace std;
int n,m,k;
const int N = 100005  ;
int tm[N];
__int64 min(__int64 a,__int64 b)
{return a<b?a:b;}
__int64 max(__int64 a,__int64 b)
{return a>b?a:b;}
class tree
{
public:
	
	int    add[4*N] ;	//延迟标记
	__int64 prime[4*N]; //存hash数的i次方
	__int64 sum[4*N]; ///存hash数的前i次方和(即系数全为1)(为操作1服务)
	__int64 hash[4*N];  //存当前节点管辖字符串的 hash值
	__int64 mod;
	__int64 fact;		//hash数-质数 
	void build(int l,int r,int i)    //  线段树的建立;  
	{  
		add[i]=0;    
		if(l==r) 
		{
			hash[i]=tm[r];
			return ;
		}
		
		int mid=(l+r)>>1;  
		build(l,mid,i<<1);  
		build(mid+1,r,i<<1|1);   
		hash[i]=(hash[i<<1]+prime[mid-l+1]*hash[i<<1|1])%mod;
	}  
	
	void pushDown(int i, int l, int r)		//把i节点的延迟标记传递到左右儿子节点
	{
		if(add[i] != 0)
		{
			int mid = (l + r) >> 1;
			add[i << 1] = add[i];
			add[i << 1 | 1] = add[i];
			
			hash[i << 1] = (sum[(mid-l+1)-1] * add[i])%mod;  
			hash[i << 1 | 1]=(sum[ r-(mid+1)+1 -1 ] * add[i])%mod;   
			add[i] = 0;
		}
	}
	
	void update(int i, int l, int r, int ql, int qr, __int64 val) //更新区间为qlqr,当前区间为l,r,代表当前区间和的节点为i,更新值为val,
	{
		if(l > qr || ql > r)		//更新区间不在当前区间内
			return ;
		if(l >= ql && r <= qr)	//要更新的区间把当前区间完全包括,则把当前整个区间+val,然后返回上一层
		{	 
			add[i] = val;
			hash[i]=(val*sum[r-l+1-1])%mod;
			return ;
		}
		pushDown(i, l, r);			
		int mid = (l + r) >> 1;
		update(i << 1, l, mid, ql, qr, val);
		update(i << 1 | 1, mid + 1, r, ql, qr, val);
		hash[i]=(hash[i<<1]+prime[mid-l+1]*hash[i<<1|1])%mod;
	}
	
	__int64 query(int i, int l, int r, int ql, int qr)	 //查询区间为qlqr,当前区间为l,r,代表当前区间和的节点为i 
	{
		if(l > qr || ql > r)
			return 0;
		if(l >= ql && r <= qr)
			return hash[i];
		pushDown(i, l, r);		
		int mid =( l + r) >> 1;
		__int64 t1= query(i << 1, l, mid, ql, qr) ;
		__int64 t2= query(i << 1 | 1, mid + 1, r, ql, qr);
		
		if (!t1) return t2%mod;//如果t1是return 0的情况,那么t2就不必乘上prime[len]了
		if (!t2) return t1%mod; 
		//return (t1+t2*prime[mid-l+1])%mod; 由于t1可能是属于return 0的情况,所以需要取max(l,ql),意义是取t1的有效长度
	
		return (t1+t2*prime[mid-max(l,ql)+1])%mod;
	} 
	void init()
	{ 
	//	memset(add,0,sizeof(add)); 
		prime[0]=1;
		sum[0]=1;
		int i;
		for (i=1;i<=N;i++)
		{
			prime[i]=(prime[i-1]*fact)%mod;
			sum[i]=(sum[i-1]+prime[i])%mod;
		}
	}
};

tree tp1;
tree tp2;

int main()
{
	
	tp1.mod=1e9+7;
	tp2.mod=1e9+9;
	tp1.fact=799817;
	tp2.fact=451309;  
	tp1.init();
	tp2.init();

	int i,op,l,r,d;
	
    scanf("%d%d%d", &n,&m,&k);
	 
		for (i=1;i<=n;i++) 
		{
			scanf("%1d",&tm[i]);  
		 	tm[i]+='0';
		}
	tp1.build(1,n,1);
	tp2.build(1,n,1);

	for (i=1;i<=m+k;i++)
	{
		scanf("%d %d %d %d",&op,&l,&r,&d);
		if (op==1)
		{
			tp1.update(1,1,n,l,r,d+'0');
			tp2.update(1,1,n,l,r,d+'0');				 
		}
		else
		{
			if (r-l+1==d)
			{
				printf("YES\n");
				continue;
			}
			else
			{
				
				int l1=l;
				int r1=r-d;
				int l2=l+d;
				int r2=r; 
				__int64 t1=tp1.query(1,1,n,l1,r1);
				__int64 t2=tp1.query(1,1,n,l2,r2);
				__int64 t3=tp2.query(1,1,n,l1,r1);
				__int64 t4=tp2.query(1,1,n,l2,r2);
				if (t1!=t2 || t3!=t4)//任一hash函数的值不匹配
				{
					printf("NO\n");
				}
				else
				{
					printf("YES\n");
				} 
			}
		}	
	}
	
	return 0;
}







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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值