您的位置:首页 > 运维架构

BZOJ 3942 浅谈线段树维护哈希值+KMP优化暴力匹配

2017-09-22 15:29 585 查看


世界真的很大

这道题一开始就想了哈希,理论上是可以过的。。

提莫的这道题卡哈希

无可奈何写正解

看题先:

description:

有一个S串和一个T串,长度均小于1,000,000,设当前串为U串,
然后从前往后枚举S串一个字符一个字符往U串里添加,若U串后缀为T,则去掉这个后缀继续流程。


input:

The first line will contain S. The second line will contain T. The length of T will be at most that of S,
and all characters of S and T will be lower-case alphabet characters (in the range a..z).


output:

The string S after all deletions are complete. It is guaranteed that S will not become empty during the deletion process.


这道题一看就先想暴力

每次匹配前面有没有后缀等于t串

发现其实我们就只是想快速知道前面n的长度是不是T串而已

而字符串这个东西不太好比较,只能一个一个的比

比较不好比较的东西,哈希

于是就想到用线段树来维护一个哈希值,每次添加,修改,查询

复杂度 nlogn

然后这道题提莫的卡哈希

当然有可能是我哈希写丑了,反正我双哈希都没过

哈希写出来了的同学可以留言。。

完整代码:

#include<stdio.h>
#include<string.h>
typedef unsigned long long unt;

const unt base=9804799;

unt p[1000010];

struct node
{
unt hash;
int siz;
node *ls,*rs;
void update()
{
hash=ls->hash+rs->hash*p[ls->siz];
siz=ls->siz+rs->siz;
}
}pool[4000010],*tail=pool,*root,*root2;

char ss[1000010],t[1000010],ans[1000010];

node *build(int lf,int rg)
{
node *nd=++tail;
if(lf==rg)
{
nd->hash=0;
nd->siz=1;
nd->ls=nd->rs=0;
return nd;
}
int mid=(lf+rg)>>1;
nd->ls=build(lf,mid);
nd->rs=build(mid+1,rg);
nd->update();
return nd;
}

void modify(node *nd,int lf,int rg,int pos,char K)
{
if(lf==rg)
{
nd->hash=K-'a'+1;
return ;
}
int mid=(lf+rg)>>1;
if(pos<=mid) modify(nd->ls,lf,mid,pos,K);
else modify(nd->rs,mid+1,rg,pos,K);
nd->update();
}

unt query(node *nd,int lf,int rg,int L,int R)
{
if(L<=lf&&rg<=R)
return nd->hash;
int mid=(lf+rg)>>1;
if(R<=mid) return query(nd->ls,lf,mid,L,R);
if(L>mid) return query(nd->rs,mid+1,rg,L,R);
unt lt=query(nd->ls,lf,mid,L,mid);
unt rt=query(nd->rs,mid+1,rg,mid+1,R);
return lt+rt*p[mid-L+1];
}

int main()
{
scanf("%s%s",ss,t);p[0]=1;
int n=strlen(ss),m=strlen(t);
for(int i=1;i<=n;i++)
p[i]=p[i-1]*base;
root=build(0,n);
unt tmp=0;
for(int i=0;i<m;i++)
tmp+=p[i]*(t[i]-'a'+1);
int p=-1;
for(int i=0;i<n;i++)
{
ans[++p]=ss[i];
modify(root,0,n,p,ss[i]);
if(p<m) continue;
unt tmp2=query(root,0,n,p-m+1,p);
if(tmp2==tmp) p-=m;
}
for(int i=0;i<=p;i++)
printf("%c",ans[i]);
return 0;
}


好了现在来谈正解

如果是在考场上碰见这道题我肯定就直接写hash了,好写好调

但是自己刷题总还是要考虑正解的,尤其是在知道hash被卡的情况下

然后对着题目又想了一下,是不是就是KMP来处理那些没有重叠的串呢?

看样子不是的,因为样例里面很清楚的给出来了,还有可能去掉一个串后使得后面的串合并为新的模式串

之所以KMP不能处理这样的情况,是因为KMP在匹配完一个位置后不会记录这个位置匹配到哪里,而是继续匹配,换言之,无法回头

那怎么让他回头呢?记录每一个位置匹配到原串的哪一个位置就好了,就可以模拟KMP的匹配过程了,而且匹配成功之后还可以直接回到之前的位置,因为是记录了匹配位置在哪里的

完整代码:

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;

int n,m,top=0,nxt[1000010],a[1000010];
char ss[1000010],t[1000010],stk[1000010];

void calnxt(char *s)
{
nxt[0]=-1;
int i=0,j=-1;
while(i<n)
{
if(j==-1 || s[i]==s[j])
{
i++,j++;
nxt[i]=j;
}
else j=nxt[j];
}
}

int main()
{
scanf("%s%s",ss,t);
n=strlen(t),m=strlen(ss);
calnxt(t);
for(int i=0;i<m;i++)
{
stk[++top]=ss[i];
int tmp=a[top-1];
while(tmp && stk[top]!=t[tmp])
tmp=nxt[tmp];
if(stk[top]==t[tmp]) tmp++;
tmp=max(tmp,0);
a[top]=tmp;
if(tmp==n) top-=tmp;

}
for(int i=1;i<=top;i++)
printf("%c",stk[i]);
return 0;
}
/*
Whoso pulleth out this sword from this stone and anvil is duly born King of all England
*/


嗯,就是这样
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: