BZOJ_1014_[JSOI2008]_火星人prefix_(Splay+LCP_Hash+二分)

描述



http://www.lydsy.com/JudgeOnline/problem.php?id=1014

给出一个字符串,有修改,插入,以及询问LCP(i,j)的操作.

分析



LCP在白书上面有介绍,\(LCP(i,j)\)表示以第\(i\)位和以第\(j\)位开头的后缀的最长公共前缀.

先考虑没有插入和修改操作的问题.我们可以用基于Hash的LCP算法.

我们给每一个后缀一个Hash值.其中以第\(i\)为开头的后缀的Hash值为\(H[i]=H[i+1]x+s[i]\).

其中\(x\)是随便一个什么数.例如:

\(H[4]=s[4]\)

\(H[3]=s[4]x+s[3]\)

\(H[2]=s[4]x^2+s[3]x+s[2]\)

\(H[1]=s[4]x^3+s[3]x^2+s[2]x+s[1]\)

一般地有:

$$H[i]=s[n]x^{n-i}+s[n-1]x^{n-1-i}+...+s[i+1]x+s[i]$$

对于字符串\(s[i]~s[i+L-1]\)(长度为L),定义它的Hash值为:

$$Hash(i,L)=H[i]-H[i+L]x^L$$

其实就是相当于把以\(i\)开头的后缀的Hash值有关\(i+L\)以及后面的部分都砍掉.

当然这个Hash值可以定义为前缀的形式,和后缀的没有区别.

但是注意,并不是字符串不同,Hash值一定不同,只是相同的概率极低,基本可以无视.

至于计算,我们采用unsigned long long ,这样自然溢出相当于对\(2^{64}\)取模.

这样我们就可以判断字串\((i,L)\)与\((j,L)\)是否相等,二分\(L\)的值,取最大可行解即可.

至于修改和插入操作?平衡树来解决咯.平衡树上每个结点的Hash值代表的是以该结点为根的子树代表的字符串的Hash值.

p.s.

1.复习了下Splay,好不熟练啊,还要多练习,不然药丸...

2.调了好久发现是字符串读入的问题...(拍脸)

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3
 4 const int maxn=1e5+5;
 5 typedef unsigned long long ull;
 6 int n,m;
 7 ull p[maxn];
 8 char s[maxn];
 9 struct Splay{
10     struct node{
11         node* c[2],* f;
12         int v,s; ull h;
13         node(int v,node* t):v(v){ s=1;h=v;f=c[0]=c[1]=t; }
14         bool d(){ return f->c[1]==this; }
15         void setc(node* x,bool d){ c[d]=x; x->f=this; }
16         void push_up(){
17             s=c[0]->s+c[1]->s+1;
18             h=c[0]->h+(ull)v*p[c[0]->s]+c[1]->h*p[c[0]->s+1];
19         }
20     }* root,* null;
21     Splay(){
22         null=new node(0,0);null->s=0;
23         root=new node(0,null); root->setc(new node(0,null),1);
24     }
25     void rot(node* x){
26         node* f=x->f; bool d=x->d();
27         f->f->setc(x,f->d());
28         f->setc(x->c[!d],d);
29         x->setc(f,!d);
30         f->push_up();
31         if(f==root) root=x;
32     }
33     void splay(node* x,node *f){
34         while(x->f!=f)
35             if(x->f->f==f) rot(x);
36             else x->d()==x->f->d()?(rot(x->f),rot(x)):(rot(x),rot(x));
37         x->push_up();
38     }
39     node* kth(int k){
40         for(node* t=root;t!=null;){
41             int s=t->c[0]->s;
42             if(k==s) return t;
43             if(k>s) t=t->c[1], k-=s+1;
44             else t=t->c[0];
45         }
46     }
47     node* get_range(int l,int r){
48         splay(kth(l-1),null);
49         splay(kth(r+1),root);
50         return root->c[1]->c[0];
51     }
52     void ins(int v,int pos){
53         node *f=get_range(pos,pos);
54         f->setc(new node(v,null),1); splay(f->c[1],null);
55     }
56     void chg(int v,int pos){
57         node *x=get_range(pos,pos);
58         x->v=v; splay(x,null);
59     }
60     int hash(int l,int r){ return get_range(l,r)->h; }
61     node* build(int l,int r){
62         if(l>r) return null;
63         int m=l+(r-l)/2;
64         node *t=new node(s[m]-‘a‘+1,null);
65         t->setc(build(l,m-1),0);
66         t->setc(build(m+1,r),1);
67         t->push_up();
68         return t;
69     }
70 }T;
71 inline int read(int &x){ x=0;int k=1;char c;for(c=getchar();c<‘0‘||c>‘9‘;c=getchar())if(c==‘-‘)k=-1;for(;c>=‘0‘&&c<=‘9‘;c=getchar())x=x*10+c-‘0‘;return x*=k; }
72 inline char read(char &c){ for(c=getchar();(c<‘a‘||c>‘z‘)&&(c<‘A‘||c>‘Z‘);c=getchar());return c; }
73 int bsearch(int x,int y){
74     int s=T.root->s-2;
75     int l=0,r=min(s-x,s-y)+1,mid;
76     while(l<r){
77         mid=l+(r-l+1)/2;
78         if(T.hash(x,x+mid-1)==T.hash(y,y+mid-1)) l=mid;
79         else r=mid-1;
80     }
81     return l;
82 }
83 void init(){
84     scanf("%s",s+1); n=strlen(s+1); read(m);
85     p[0]=1;
86     for(int i=1;i<maxn;i++) p[i]=p[i-1]*(ull)27;
87     T.root->c[1]->setc(T.build(1,n),0); T.root->c[1]->push_up(); T.root->push_up();
88 }
89 int main(){
90     init();
91     while(m--){
92         char c; int x,y;
93         read(c); read(x);
94         if(c==‘Q‘){ read(y); printf("%d\n",bsearch(x,y)); }
95         else if(c==‘R‘) T.chg(read(c)-‘a‘+1,x);
96         else T.ins(read(c)-‘a‘+1,x);
97     }
98     return 0;
99 }

时间: 2024-10-11 12:10:15

BZOJ_1014_[JSOI2008]_火星人prefix_(Splay+LCP_Hash+二分)的相关文章

【bzoj1014】[JSOI2008]火星人prefix Splay+Hash+二分

题目描述 火星人最近研究了一种操作:求一个字串两个后缀的公共前缀.比方说,有这样一个字符串:madamimadam,我们将这个字符串的各个字符予以标号:序号: 1 2 3 4 5 6 7 8 9 10 11 字符 m a d a m i m a d a m 现在,火星人定义了一个函数LCQ(x, y),表示:该字符串中第x个字符开始的字串,与该字符串中第y个字符开始的字串,两个字串的公共前缀的长度.比方说,LCQ(1, 7) = 5, LCQ(2, 10) = 1, LCQ(4, 7) = 0

[BZOJ1014][JSOI2008]火星人prefix splay+hash+二分

题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1014 先考虑如果没有修改操作和插入操作,是一个静态的字符串,我们可以怎样快速求得题目中的LCQ. 两个字符串判等很容易想到hash.于是我们二分答案并二分判断,就可以在$log_n$时间内得到答案. 现在加上修改和插入操作,其实就是要动态维护子串也就是一段区间的hash值,这种问题很容易就想到用splay来维护. 每个节点记录此节点管辖下子树的hash值h,当前节点的h=左孩子的h+节点

【BZOJ1014】【JSOI2008】火星人prefix Splay处理区间,hash+dichotomy(二分)check出解

题意不赘述了,太清晰了. 说题解:首先根据原字符串建立SPT,首尾建议多加一个空白字符. 给一个树构图,按照平衡树的前后大小顺序性质可以使它们始终维持为一个序列,并且可以通过rank找到序列的第k个. 树构造完了以后,点插入,点修改,询问神马的代码里都有详细注释. /* BZOJ 1014 新手看的时候建议从main函数处开始,按照运行顺序来脑模拟. P.S. 这个代码的hash用的是自然溢出而非取mod运算. */ #include <cstdio> #include <cstdlib

BZOJ 1014 JSOI2008 火星人prefix Splay+Hash+二分

题目大意:给定一个字符串,提供下列操作: 1.查询从x开始的后缀和从y开始的后缀的最长公共前缀长度 2.将x位置的字符修改为y 3.在x位置的字符后面插入字符y 看到这题一开始我先懵住了...这啥..我第一时间想到的是后缀数据结构 但是不会写 而且后缀数据结构也不支持修改操作 后来无奈找了题解才知道是Hash+二分... 太强大了 Hash+二分打爆一切啊 用Splay维护这个字符串的修改和插入操作 每个节点维护子串的Hash值 判断时二分找到最长公共前缀 不过这道题还有一些注意事项 1.此题不

[BZOJ1014] [JSOI2008] 火星人prefix (splay &amp; 二分答案)

Description 火星人最近研究了一种操作:求一个字串两个后缀的公共前缀.比方说,有这样一个字符串:madamimadam,我们将这个字符串的各个字符予以标号:序号: 1 2 3 4 5 6 7 8 9 10 11 字符 m a d a m i m a d a m 现在,火星人定义了一个函数LCQ(x, y),表示:该字符串中第x个字符开始的字串,与该字符串中第y个字符开始的字串,两个字串的公共前缀的长度.比方说,LCQ(1, 7) = 5, LCQ(2, 10) = 1, LCQ(4,

【BZOJ】1014: [JSOI2008]火星人prefix(splay+hash+二分+lcp)

http://www.lydsy.com/JudgeOnline/problem.php?id=1014 被sb错调哭了QAQ...insert那里..插入到第x个后边...我......写成了第x个前面..........还调了!好!久! QAQ 本题神lcp做法....表示只会sa的height的离线.......这种在线的我就QAQ做个忧伤的表情... 然后膜拜了题解....好神..splay维护区间...hash+二分维护lcp....QAQ似乎是白书上说的么... 但是这种有概率的题这

bzoj1014: [JSOI2008]火星人prefix(splay+hash+二分)

题目大意:一个字符串三个操作:①求两个后缀的LCP②插入一个字符③修改一个字符. 前几天刚学了hash+二分求lcp,就看到这题. 原来splay还能这么用?!原来splay模板这么好写?我以前写的splay是假的吧woc splay每个节点代表一个字符,并维护这个子树代表一个子串的哈希值.因为splay旋转不破坏树结构,所以不论怎么旋转这棵splay树都能代表这个字符串. 预处理处理少了调了半天呜呜呜 赶紧跑去更新自己的splay模板 #include<iostream> #include&

BZOJ-1014 [JSOI2008]火星人prefix (Splay+二分+hash)

题解: 用splay维护添加修改操作,然后二分hash判断长度. 操作一:对于查询区间[l,r]的hash值,显然将l-1旋到根,将r+1旋到根的右儿子,此时所求区间就是根的右儿子的左儿子了. 操作二:将要修改的位置旋到根,然后直接改就可以了. 操作三:要在x后面添加一个字符,显然将x旋到根,x+1旋到根的右儿子,然后直接加在根的右儿子的左儿子上就可以了. 1 #include<iostream> 2 #include<cstdlib> 3 #include<cstdio&g

bzoj 1014 [JSOI2008]火星人prefix (splay+二分答案+字符串hash)

题目大意:维护一个字符串,支持插入字符和替换字符的操作,以及查询该字符串两个后缀的最长公共前缀长度 乍一看以为是后缀数组,然而并没有可持久化后缀数组(雾) 看题解才知道这是一道splay题,首先要对splay维护区间信息有一定了解 splay维护,插入字符,替换字符 而它的字树内所有儿子的中序遍历的hash值也可以通过splay维护  (这个推导式似乎烂大街了) 而后缀就是把i-1拎到根节点,然后把n+1拎到根节点的右儿子上,它的左儿子表示的就是hash值 至于如何查公共前缀呢?二分答案啊!询问