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;}
- CF#321-DIV2-E. Kefa and Watch-线段树+字符串哈希
- codeforces 321# E. Kefa and Watch (线段树+字符串hash)
- 【Code Forces 321E】【线段树+字符串哈希】Kefa and Watch 字符串修改 循环节判定
- codeforces 580 E. Kefa and Watch (字符串hash + 线段树)
- Codeforces #321 E. Kefa and Watch (线段树、哈希)
- Codeforces Round #321 Kefa and Watch(字符串哈希+线段树)
- Codeforces 580E Kefa and Watch 线段树+双值Hash
- Codeforces 580E Kefa and Watch 线段树
- codeforces 580 E. Kefa and Watch(hash+线段树)
- CodeForces 580 E.Kefa and Watch(hash+线段树)
- Codeforces Round #321 (Div. 2) E - Kefa and Watch(hash + 线段树)
- CODEFORCES ROUND #321 (DIV. 2) E.Kefa and Watch(线段树+hash)
- 字符串hash+线段树 Codeforces580E Kefa and Watch
- 查询[l,r]区间数字串是不是周期为d的串 线段树区间修改+双重hash Codeforces #321 E. Kefa and Watch
- CODEFORCES 580E Kefa and Watch
- codeforces580E. Kefa and Watch
- cf/Codeforces Round #373 div1-C/div2-E Sasha and Array 线段树 + 维护矩阵快速幂
- B. Kefa and company(#321 div2)
- 关于环境变量
- SQL查询 - RowNumber、循环、行列转换
- 使用IB初始化视图控制器
- struct linger 用法
- java SE复习笔记52
- CF#321-DIV2-E. Kefa and Watch-线段树+字符串哈希
- hdu 3294 Girls' research(manacher)
- 谈扫描的行数与访问类型-mysql
- 联想+A916+原版官方稳定精简ROM
- Css用法
- EventBus
- the partition for installation
- How to fix? Unable to locate theme engine in module_path: “murrine”
- CCS3.3的使用2