社区讨论

对于S较大的(非满分)字符串哈希解法,望诸君集思广益解决该算法正确性问题

P6080[USACO05DEC] Cow Patterns G参与者 1已保存回复 0

讨论操作

快速查看讨论及其快照的属性,并进行相关操作。

当前回复
0 条
当前快照
1 份
快照标识符
@mitr23pj
此快照首次捕获于
2025/12/06 11:44
2 个月前
此快照最后确认于
2025/12/07 22:15
2 个月前
查看原帖
本思路正确仅当A中的数互不相同,B中的数互不相同.
本思路在Gxyz(即学校OJ)拿到了88pts,luogu拿到54pts.
本思路的核心是权值线段树/Splay/Treap维护全局字符串哈希.
定义字符串函数B->sB,len(sB)=len(B),sB[i]是数组B中排名为i的下标,在上面限定的条件下,两字符串A,B相等仅当sA=sB.
于是思路是高效求sB和sA[i,i+len(B)]的哈希值并比较.
显然可以用动态开点权值线段树/Splay/Treap求sB和sA[i,i+len(B)].以权值线段树为例,维护方法是类似单调队列在线段树上a[i]的位置插入/删除i并逐层合并哈希值.
令size[x]是线段树x结点的子树下有多少个位置有元素.合并方法:size[x]=size[ls[x]]+size[rs[x]],hash[x]=hash[ls[x]]*pow(base,size[rs[x]])+hash[rs[x]].证明可以考虑求子串哈希值公式.
只需比较hash[root]与事先按类似方法求的sB即可.
但是稍微想想即可发现hash[root]对应的是sA[i,i+len(B)]'={sA[i,i+len(B)][i]+i}的哈希值.要想得到sA[i,i+len(B)]的哈希值考虑代数变形.
结论:hash(sA[i,i+len(B)])=hash(sA[i,i+len(B)]')-iS.其中S=1+base+base*base+......+pow(base,len(B)-1).证明可以考虑字符串哈希的定义展开.
按照上述的方法比较,检测到相等记录答案即可.
由于本人的语言表达能力不强并且代码实现有细节与上述思路不同,下附代码:
CPP
#include<iostream> 
#include<algorithm>
#include<cmath>
using namespace std;
int ans[500000],cnt,d;
int ls[2000001],rs[2000001],v[2000001];
long long n,k,s,x,a[500000],b[500000],r[500000];
unsigned __int128 hs,h[2000001],br,pw[500001];
//base=31,usigned__int128自然溢出哈希
int nw(){
	d++;return d;
}//动态开点
//线段树单点修改
void upd(int ix,long long sl,long long sr,long long x,int c){
	if(sl!=sr){
		if(x<=(sl+sr)>>1){
			if(!ls[ix])ls[ix]=nw();
			upd(ls[ix],sl,(sl+sr)>>1,x,c);
		}else{
			if(!rs[ix])rs[ix]=nw();
			upd(rs[ix],((sl+sr)>>1)+1,sr,x,c);
		}v[ix]=v[ls[ix]]+v[rs[ix]];
		h[ix]=h[ls[ix]]*pw[v[rs[ix]]]+h[rs[ix]];
    //合并标记
	}else v[ix]++,h[ix]=c;
	return;
}//添加一个数
void del(int ix,long long sl,long long sr,long long x){
	if(sl!=sr){
		if(x<=(sl+sr)>>1){
			if(!ls[ix])ls[ix]=nw();
			del(ls[ix],sl,(sl+sr)>>1,x);
		}else{
			if(!rs[ix])rs[ix]=nw();
			del(rs[ix],((sl+sr)>>1)+1,sr,x);
		}v[ix]=v[ls[ix]]+v[rs[ix]];
		h[ix]=h[ls[ix]]*pw[v[rs[ix]]]+h[rs[ix]];
	}else v[ix]--,h[ix]=0;
	return;
}//删除一个数.
int main(){
	scanf("%lld%lld%lld",&n,&k,&s),d=1;
	for(int i=0;i<n;i++)scanf("%lld",&x),a[i]=n*(x-1)+i;
	for(int i=0;i<k;i++)scanf("%lld",&x),b[i]=k*(x-1)+i,r[i]=b[i];
	sort(r,r+k);
	for(int i=0;i<k;i++)b[i]=lower_bound(r,r+k,b[i])-r;
	for(int i=0;i<k;i++)r[b[i]]=i;
	for(int i=0;i<k;i++)hs=hs*31+r[i];
	pw[0]=1;for(int i=1;i<=k;i++)pw[i]=31*pw[i-1];
	for(int i=0;i<k;i++)br+=pw[i];//求S.
	for(int i=0;i<n;i++)r[i]=a[i];
	sort(r,r+n);
	for(int i=0;i<n;i++)a[i]=lower_bound(r,r+n,a[i])-r;
	for(int i=0;i<k-1;i++)upd(1,0,n-1,a[i],i);//先添加i<k-1的(a[i],i).
	for(int i=k-1;i<n;i++){
		upd(1,0,n-1,a[i],i);
		if(!((h[1]-(i-k+1)*br)^hs))ans[cnt]=i-k+2,cnt++;//!(a^b)就是a==b,但(仅限本人认为)常数小.
		del(1,0,n-1,a[i-k+1]);//一边添加一边删除.
	}printf("%d\n",cnt);
	for(int i=0;i<cnt;i++)printf("%d\n",ans[i]);
	return 0;
}
关于为什么这个方法在不满足A中的数互不相同,B中的数互不相同时可能不成立:
字符串sA维护的顺序关系是A[i]为第一关键字,i为第二关键字的排序.当插入顺序相同时,sA不关心A[i]是否相同.
如{1,2,3}和{1,1,3}按照定义不相同,但是它们对应的s是相同的.除非确定A中的数互不相同,B中的数互不相同,否则无法规避这种情况.
如果读者有规避这种情况的有关想法,可以集思广益一同凑正解.

回复

0 条回复,欢迎继续交流。

正在加载回复...