【bzoj1014】[JSOI2008]火星人prefix

1014: [JSOI2008]火星人prefix

Time Limit: 10 Sec  Memory Limit: 162 MB
Submit: 6031  Solved: 1917
[Submit][Status][Discuss]

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, 7) = 0 在研究LCQ函数的过程
中,火星人发现了这样的一个关联:如果把该字符串的所有后缀排好序,就可以很快地求出LCQ函数的值;同样,
如果求出了LCQ函数的值,也可以很快地将该字符串的后缀排好序。 尽管火星人聪明地找到了求取LCQ函数的快速
算法,但不甘心认输的地球人又给火星人出了个难题:在求取LCQ函数的同时,还可以改变字符串本身。具体地说
,可以更改字符串中某一个字符的值,也可以在字符串中的某一个位置插入一个字符。地球人想考验一下,在如此
复杂的问题中,火星人是否还能够做到很快地求取LCQ函数的值。

Input

  第一行给出初始的字符串。第二行是一个非负整数M,表示操作的个数。接下来的M行,每行描述一个操作。操
作有3种,如下所示
1、询问。语法:Qxy,x,y均为正整数。功能:计算LCQ(x,y)限制:1<=x,y<=当前字符串长度。
2、修改。语法:Rxd,x是正整数,d是字符。功能:将字符串中第x个数修改为字符d。限制:x不超过当前字
符串长度。
3、插入:语法:Ixd,x是非负整数,d是字符。功能:在字符串第x个字符之后插入字符d,如果x=0,则在字
符串开头插入。限制:x不超过当前字符串长度

Output

  对于输入文件中每一个询问操作,你都应该输出对应的答案。一个答案一行。

Sample Input

madamimadam
7
Q 1 7
Q 4 8
Q 10 11
R 3 a
Q 1 7
I 10 a
Q 2 11

Sample Output

5
1
0
2
1

HINT

1、所有字符串自始至终都只有小写字母构成。

2、M<=150,000

3、字符串长度L自始至终都满足L<=100,000

4、询问操作的个数不超过10,000个。

对于第1,2个数据,字符串长度自始至终都不超过1,000

对于第3,4,5个数据,没有插入操作。

【题解】

用splay维护字符串,把一段子串用哈希值表示,两个长度相同的子串如果哈希值相同,那么它们就相同。

建树时每棵子树的哈希值集中在根节点上,即h[x]=(h[left[x]]+v[x]*p[size[left]]+h[r]*p[size[left]+1])%mod

查询时二分一个答案,然后验证。

那么如何找到子串的哈希值呢?

首先找到子串的首字母,把它伸展到根,然后找到子串的尾字母的下一个字母,把它伸展到根的右儿子,那么根的右儿子的左儿子的哈希值就是答案。

吐槽:这题交了很多遍,都是RE,然后我才知道BZOJ上用cin会RE......

  1 #include<iostream>
  2 #include<cstdio>
  3 #include<cstdlib>
  4 #include<cstring>
  5 #include<cmath>
  6 #include<ctime>
  7 #include<algorithm>
  8 using namespace std;
  9 #define MAXN 150010
 10 #define ll long long
 11 #define mod 9875321
 12 int n,m,rt,sz,p[MAXN],c[MAXN][2],fa[MAXN],size[MAXN],h[MAXN],v[MAXN];
 13 char ch[MAXN];
 14 namespace INIT
 15 {
 16 inline int read()
 17 {
 18     int x=0,f=1;  char ch=getchar();
 19     while(!isdigit(ch))  {if(ch==‘-‘)  f=-1;  ch=getchar();}
 20     while(isdigit(ch))  {x=x*10+ch-‘0‘;  ch=getchar();}
 21     return x*f;
 22 }
 23 }using namespace INIT;
 24 namespace SPLAY
 25 {
 26 void updata(int x)
 27 {
 28     int l=c[x][0],r=c[x][1];
 29     size[x]=size[l]+size[r]+1;
 30     h[x]=h[l]+(ll)v[x]*p[size[l]]%mod+(ll)p[size[l]+1]*h[r]%mod;
 31     h[x]%=mod;
 32 }
 33 void rotate(int x,int &k)
 34 {
 35     int y=fa[x],z=fa[y],l,r;
 36     if(c[y][0]==x)  l=0;  else l=1;  r=l^1;
 37     if(y==k)  k=x;
 38     else {if(c[z][0]==y)  c[z][0]=x;  else c[z][1]=x;}
 39     fa[x]=z;  fa[y]=x;  fa[c[x][r]]=y;
 40     c[y][l]=c[x][r];  c[x][r]=y;
 41     updata(y);  updata(x);
 42 }
 43 void Splay(int x,int &k)
 44 {
 45     while(x!=k)
 46     {
 47         int y=fa[x],z=fa[y];
 48         if(y!=k)
 49         {
 50             if((c[y][0]==x)^(c[z][0]==y))  rotate(x,k);
 51             else rotate(y,k);
 52         }
 53         rotate(x,k);
 54     }
 55 }
 56 void build(int k,int l,int r)
 57 {
 58     if(l>r)  return;
 59     if(l==r)
 60     {
 61         v[l]=h[l]=ch[l]-‘a‘+1;
 62         fa[l]=k;  size[l]=1;
 63         if(l<k)  c[k][0]=l;
 64         else c[k][1]=l;
 65         return;
 66     }
 67     int mid=(l+r)/2;
 68     build(mid,l,mid-1);  build(mid,mid+1,r);
 69     v[mid]=ch[mid]-‘a‘+1; fa[mid]=k;  updata(mid);
 70     if(mid<k)  c[k][0]=mid;
 71     else c[k][1]=mid;
 72 }
 73 int find(int k,int rank)
 74 {
 75     int l=c[k][0],r=c[k][1];
 76     if(size[l]+1==rank)  return k;
 77     else if(size[l]>=rank)  return find(l,rank);
 78     else return find(r,rank-size[l]-1);
 79 }
 80 int check(int k,int val)
 81 {
 82     int x=find(rt,k),y=find(rt,k+val+1);
 83     Splay(x,rt);  Splay(y,c[x][1]);
 84     int z=c[y][0];
 85     return h[z];
 86 }
 87 int ask(int x,int y)
 88 {
 89     int l=1,r=min(sz-x,sz-y)-1,ans=0;
 90     while(l<=r)
 91     {
 92         int mid=(l+r)/2;
 93         if(check(x,mid)==check(y,mid))   {l=mid+1;  ans=mid;}
 94         else r=mid-1;
 95     }
 96     return ans;
 97 }
 98 void insert(int k,int val)
 99 {
100     int x=find(rt,k+1),y=find(rt,k+2);
101     Splay(x,rt);  Splay(y,c[x][1]);
102     int z=++sz;  c[y][0]=z;  fa[z]=y;  v[z]=val;
103     updata(z);  updata(y);  updata(x);
104 }
105 }using namespace SPLAY;
106 int main()
107 {
108     //freopen("cin.in","r",stdin);
109     //freopen("cout.out","w",stdout);
110     scanf("%s",ch+2);
111     n=strlen(ch+2);   p[0]=1;
112     for(int i=1;i<=MAXN;i++)  p[i]=p[i-1]*27%mod;
113     build(0,1,n+2);  rt=(n+3)/2;  sz=n+2;
114     m=read();   int x,y;
115     char s[2],d[2];
116     for(int i=1;i<=m;i++)
117     {
118         scanf("%s",s+1);  x=read();
119         if(s[1]==‘Q‘)  {y=read();  printf("%d\n",ask(x,y));}
120         if(s[1]==‘R‘)  {scanf("%s",d+1);  x=find(rt,x+1);  Splay(x,rt);  v[rt]=d[1]-‘a‘+1;  updata(rt);}
121         if(s[1]==‘I‘)  {scanf("%s",d+1);  insert(x,d[1]-‘a‘+1);}
122     }
123     return 0;
124 }

附makedata程序(博主很良心,知道你会wa):

 1 #include<iostream>
 2 using namespace std;
 3 char ch[5]={‘Q‘,‘R‘,‘I‘};
 4 int main()
 5 {
 6     freopen("cin.in","w",stdout);
 7     srand(time(NULL));
 8     int n=100;
 9     for(int i=1;i<=n;i++)  printf("%c",(char)(‘a‘+rand()%26));
10     printf("\n");
11     int m=1500;
12     printf("%d\n",m);
13     for(int i=1;i<=m;i++)
14     {
15         char f=ch[rand()%3];
16         if(f==‘Q‘)  {printf("%c %d %d\n",f,rand()%n+1,rand()%n+1);}
17         if(f==‘R‘)  {printf("%c %d %c\n",f,rand()%n+1,(char)(‘a‘+rand()%26));}
18         if(f==‘I‘)  {printf("%c %d %c\n",f,rand()%n+1,(char)(‘a‘+rand()%26));}
19     }
20     return 0;
21 }
时间: 2024-10-15 14:05:53

【bzoj1014】[JSOI2008]火星人prefix的相关文章

bzoj千题计划106:bzoj1014 [JSOI2008]火星人prefix

http://www.lydsy.com/JudgeOnline/problem.php?id=1014 两个后缀的最长公共前缀:二分+hash 带修改带插入:splay维护 #include<cstdio> #include<cstring> #include<iostream> #define L 100001 typedef unsigned long long ULL; using namespace std; char s[L+4]; int tot,root

[BZOJ1014][JSOI2008]火星人prefix

试题描述 火星人最近研究了一种操作:求一个字串两个后缀的公共前缀.比方说,有这样一个字符串: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 &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,

bzoj1014: [JSOI2008]火星人prefix Hash+Splay

Splay维护Hash值,每次二分. #include<bits/stdc++.h> #define L(t) (t)->c[0] #define R(t) (t)->c[1] #define Z(t) (L(t)->s+1) #define N 100010 #define M (l+r>>1) typedef unsigned long long ull; ull a[N]; struct node{ int v,s; ull u; node* c[2]; n

[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+二分)

题目大意:一个字符串三个操作:①求两个后缀的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】火星人prefix Splay + 二分 + Hash

1014: [JSOI2008]火星人prefix Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 5852  Solved: 1871[Submit][Status][Discuss] Description 火星人最近研究了一种操作:求一个字串两个后缀的公共前缀.比方说,有这样一个字符串:madamimadam,我们将这个字符串的各个字符予以标号:序号: 1 2 3 4 5 6 7 8 9 10 11 字符 m a d a m i m a d

JSOI2008 火星人prefix

1014: [JSOI2008]火星人prefix Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 2918  Solved: 866[Submit][Status] Description 火星人最近研究了一种操作:求一个字串两个后缀的公共前缀.比方说,有这样一个字符串:madamimadam,我们将这个字符串的各个字符予以标号:序号: 1 2 3 4 5 6 7 8 9 10 11 字符 m a d a m i m a d a m 现在,火星人