poj 2758 Checking the Text

Description

Wind’s birthday is approaching. In order to buy a really really fantastic gift for her, Jiajia has to take a boring yet money-making job - a text checker.

This job is very humdrum. Jiajia will be given a string of text what is English letters and he must count the maximum number of letters that can be matched, starting from two position of the current text simultanously. The matching proceeds from left to right, one character by one.

Even worse, sometimes the boss will insert some characters before, after or within the text. Jiajia wants to write a program to do his job automatically, this program should be fast enough, because there are only few days to Wind’s birthday

Input

The first line of input file contains initial text.

The second line contains then number of commands n. And the following n lines describe each command. There are two formats of commands:

I ch p: Insert a character ch before the p-th. if p is larger than the current length of text, then insert at end of the text.

Q i j: Ask the length of matching started from the i-th and j-th character of the initial text, which doesn’t include the inserted characters.

You can assume that the length of initial text will not exceed 50000, the number of I command will not exceed 200, the number of Q command will not exceed 20000.

Output

Print one line for each Q command, contain the max length of matching.

Sample Input

abaab
5
Q 1 2
Q 1 3
I a 2
Q 1 2
Q 1 3

Sample Output

0
1
0
3

题目分析

给定一个字符串,可以对字符串进行两种操作:
插入操作:I ch p,将ch插入到原字符串第p个字符前,如果p大于字符串长度就插到最后
查询操作:Q i j,字符串中从i开始的子串,和从j开始的子串,这两个子串的LCP(最长公共前缀Longest Common Prefix)
几个字符串的LCP:
例如 “ABCD”、“ABCE”、“ACEF”的LCP是“A”
“ABCDEFG”、“ABCXYZ”、“ABCDFG”的LCP是“ABC”

比较麻烦的是,这个题故意给弄了一个条件:插入时的下标是当前下标,而查询操作时的下标是原来字符串的下标

针对Sample Input 和Sample Output的分析

输入的字符串是“abaab”
第一个命令: Q 1 2,查询从1开始的子串和从2开始的子串的LCP
从1开始的子串是“abaab”,从2开始的子串是“baab”,LCP为0
第二个命令:Q 1 3 查询从1开始的子串和从3开始的子串的LCP
两个子串分别是“abaab”和“aab”,结果是1(“a”)
第三个命令:I a 2 ,将a插入到第2个字符位置,完成后“aabaab”
第四个命令: Q 1 2,查询从1开始的子串和从2开始的子串的LCP
这里的下标是指的插入前原始的下标,因此1还是指的第一个‘a’,而2指的是原来的‘b’,但是字符串已经是插入后的新串了。从1开始的子串是“aabaab”,从2开始的子串是“baab”,LCP为0
第二个命令:Q 1 3 查询从1开始的子串和从3开始的子串的LCP
同上,两个子串分别是“aabab”和“aab”,结果是3(“aab”)

解题思路

还是通过字符串哈希来解决,从两个指定的起始位置开始,按照两个子串中短的那个串的长度,依次比较两个子串的第一个字符、前两个字符、前三个字符…是否相同,直到不同或者比较完为止。
为了提高效率,还可以采取二分的方法,比如两个串中短的那个长度为7个字符,则第一次先比较前4个字符(长度的一半)是否一样。

插入操作的次数较少,可以进行暴力维护

代码分析

用到的数据结构
  • 因为用到字符串哈希,把字符串当成p进制数,p取13131,取模时md=233333333
  • 字符数组s[]存放整个字符串
  • 数组h[]用来存放每一个hash值
  • pw[]用来存放p进制每一位的权值
  • 由于查询操作的下标是插入前的原始下标,因此用一个数组f[]保存原始下标和插入后下标的对应关系
#include<cstdio>
#include<cstring>
const  long long md=233333333; 
char s[51000];
int f[51000];
long long h[14000],pw[51000];
初始化准备
	int i,j,k,m,n,p=13131,q,x,y,z,T,l;
	 long long x1,x2;	//用于计算待比较的两个子串的hash值
	char c;
	scanf("%s",s+1);	//读入原始字符串
	scanf("%d",&T);		//待输入命令个数
	n=l=strlen(s+1);	//n和l都是字符串长度
	//--------准备好各位权值的数组pw[]--------
	pw[0]=1;
	for (i=1;i<=50500;i++)
	  pw[i]=(pw[i-1]*p)%md;
	//-------记录初始下标的位置,后续在插入时会被改变------
	for (i=1;i<=l;i++)
	  f[i]=i;
	//-------计算好字符串的每一个hash值--------
	for (i=1;i<=l;i++)
	  h[i]=(h[i-1]*p+s[i]-'a'+1)%md;	//‘a’-‘z’当成1-26处理

下面分析查询操作的实现代码,
查询时,x、y两个变量分别记录待比较的两个子串的起始坐标。
由于要用二分,所以还要有用于指向字符串左、中、右相对位置的3个变量:i、j和m。i指向两个待比较字符串的左端,初始为0;j指向两个待比较字符串的右端,初值为两个串中短的那个的长度;m指向字符串的中间位置,初值为m=(i+j+1)/2;

注:每次比较,都是比较两个字符串从起始位置(x和y)到中间位置(x+m-1和y+m-1)这一段子串是否相同,

如果相等,则让左指针i=m,m右移指向右边剩下的中间位置,继续从起始位置到m这一段是否相同,以此类推
如果不相等,则让右指针j=m-1,m左移指向左半段的中间位置,继续比较从起始位置开始到m这一段是否相同,以此类推

			x=f[x];
			y=f[y];
			i=0;
			j=min(l-x,l-y)+1;
			while (i<j)
			{
				m=(i+j+1)/2;
				x1=(h[x+m-1]-h[x-1]*pw[m]%md+md)%md;
				x2=(h[y+m-1]-h[y-1]*pw[m]%md+md)%md;
				if (x1==x2) i=m;
				else j=m-1;
			}

为了好理解,举个例子:两个字符串起始坐标分别是x=1和y=3,待比较的两个字符串中短的那个长度是5
则 左指针i=0、右指针j=min(l-x,l-y)+1=5、中间指针m=(i+j+1)/2=3
第一次比较前三个字符
对于第一个字符串,起始坐标是1,所以是比较s[1],s[2],s[3],子串区间[1…3]
hash[1…3] = ( h[3] -h[0]*pw[3] ) % mod
对于第二个字符串,起始坐标是3,所以是比较s[3],s[4],s[5],子串区间[3…5]
hash[3…5] = ( h[5] - h[2]*pw[3] ) % mod

如果这两个hash值相同,说明前三个字符一样,左指针i指向m的位置 i=m=3,而m指向右半段的中点 m=(i+j+1)/2=4,接下来比较两个字符串的前4个是否一样…

对于插入操作,相对就简单一些

			if (x>l+1) x=l+1;	//如果插入位置大于长度,插到最后
			for (i=l;i>=x;i--)
			{
				s[i+1]=s[i];	//插入位置后的每一个字符后移一位
			}
			s[x]=c;		//插入这个字符
			
			//将f[]数组中插入位置后的每一个字符的位置+1
			//原来f[2]=2,第2个字符就在下标2的位置,插入一个后f[2]=3表示原来第2个字符现在的坐标是3了
			for (i=n;i>=1&&f[i]>=x;i--)
			  f[i]++; 
			l++;		//字符串长度+1,n这个原始长度不变
			
			//将后续的每一个hash值重新计算一下
			for (i=x;i<=l;i++)
			  h[i]=(h[i-1]*p+s[i]-'a'+1)%md;

以下是完整代码,借鉴了冬令营尹昊萱的博客

#include<cstdio>
#include<cstring>
const  long long md=233333333;
char s[51000];
int f[51000];
 long long h[14000],pw[51000];
int min(int x,int y)
{
	return x<y?x:y;
}
int main()
{
	int i,j,k,m,n,p=13131,q,x,y,z,T,l;
	 long long x1,x2;
	char c;
	scanf("%s",s+1);
	scanf("%d",&T);
	n=l=strlen(s+1);
	pw[0]=1;
	for (i=1;i<=50500;i++)
	  pw[i]=(pw[i-1]*p)%md;
	for (i=1;i<=l;i++)
	  f[i]=i;
	for (i=1;i<=l;i++)
	  h[i]=(h[i-1]*p+s[i]-'a'+1)%md;
	while (T--)
	{
		scanf("\n%c",&c);
		if (c=='Q')
		{
			scanf("%d%d",&x,&y);
			x=f[x];
			y=f[y];
			i=0;
			j=min(l-x,l-y)+1;
			while (i<j)
			{
				m=(i+j+1)/2;
				x1=(h[x+m-1]-h[x-1]*pw[m]%md+md)%md;
				x2=(h[y+m-1]-h[y-1]*pw[m]%md+md)%md;
				if (x1==x2) i=m;
				else j=m-1;
			}
			printf("%d\n",i);
		}
		else
		{
			scanf(" %c%d",&c,&x);
			if (x>l+1) x=l+1;
			for (i=l;i>=x;i--)
			{
				s[i+1]=s[i];				
			}
			s[x]=c;
			for (i=n;i>=1&&f[i]>=x;i--)
			  f[i]++; 
			l++;
			for (i=x;i<=l;i++)
			  h[i]=(h[i-1]*p+s[i]-'a'+1)%md;
		}
	}
} 

原文:https://blog.csdn.net/sdfzyhx/article/details/51388522

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值