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

来源:互联网 发布:ubuntu 修改文件指令 编辑:程序博客网 时间:2024/05/16 06:41

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