compress words (KMP/字符串哈希,双哈希)

在这里插入图片描述
题目链接
这下好了,霍,一打开这篇,目录惊人,一道题这么多问题,显然,某人智商堪忧

连接若干单词,规避单词尾和下一个单词首的重复字段

kmp求最长相同前后缀

交到coedforce上,next数组报错,应该是同名了,保险起见改成nex

 还是对kmp算法的理解不够深刻,成功匹配时j=le,
	不成功,一种是不相等,终究还是主串比完了也没能全部匹配上
	就看最后这个j表示匹配哪儿 
	如果主串最后一个字符也未能与s[0]比较成功,j就会变为-1,继而加一得0 
	想象匹配过程中 
	abcdefgh
		 fgheg 
#include <iostream>
#include <math.h>
using namespace std;
#include <string>
const int N=1e6+10;
string t,s;
int len;//后续输入字符串的长度 
int nex[N];
void getNext(){
	int i=0;
	int j=-1;
	nex[0]=-1;
	while(i<len){
		if(j==-1||s[i]==s[j]){
			i++;
			j++;
			nex[i]=j;
		}
		else j=nex[j];
	}
}
int kmp(int st){
	int i=st,j=0;
	int le=t.size();
	while(i<le){
		while(j!=-1&&t[i]!=s[j])j=nex[j];
		i++;j++;	
	}
//	while(i<le){
//		if(j==-1||t[i]==s[j]){
//			i++;
//			j++;
//		}
//		else j=next[j];
//	}
	return j;
//还是对kmp算法的理解不够深刻,成功匹配时j=le,
//	不成功,一种是不相等,终究还是主串比完了也没能全部匹配上
//	就看最后这个j表示匹配哪儿 
//	如果主串最后一个字符也未能与s[0]比较成功,j就会变为-1,继而加一得0 
//	想象匹配过程中 
//	abcdefgh
//		 fgheg 
}
int main(){
	int n;
	cin>>n;
	cin>>t;
	for(int i=1;i<n;i++){
		cin>>s;
		len=s.size();
		getNext();
		int st;//成型字符串的开始匹配位置 ,不必回回从0开始
		st=t.size()-len; 
		st=max(0,st);
		int pos=kmp(st); 
		t+=s.substr(pos,len-pos+1);
	}
	cout<<t;
	return 0;
}

字符串双哈希

思路:

把第一个单词当作所求字符串t
之后输入一个单词s后便算出这个单词的字符串前缀哈希,
由 于 重 叠 的 长 度 是 m i n ( s t r l e n ( t ) , s t r l e n ( s ) ) , 由于重叠的长度是min(strlen(t),strlen(s)), min(strlen(t),strlen(s)),
在s中这段长度,分别比较 s的前缀哈希值和
t对应后缀段的哈希值,确定要将s的某段加入t,进一步求出t完整段的哈希值一遍getH取得后缀段的哈希值

WC我没想到这道题耗了我如此之久
问题一堆:

问题一

不断拼接一段字符串 s s s到原有字符串 t t t上,总共长度数据范围是 1 e 6 1e6 1e6,为了求已有长度字符串的哈希值,反复使用 s t r l e n ( t + 1 ) strlen(t+1) strlent+1
n n n的范围是 1 e 5 1e5 1e5,strlen时间复杂度是 O ( n ) O(n) O(n), 外层循环+内部 s t r l e n , 复 杂 度 O ( n 2 ) strlen,复杂度O(n2) strlen,O(n2)循环里面还用strlen真的是超时到绝望
strlen

	scanf("%s",t+1);
	int st=1;
	for(int j=1;j<n;j++){
		scanf("%s",s+1);
		int len=strlen(t+1);
		int le=strlen(s+1);
		for(int i=st;i<=len;i++){
			int id=getID(t[i]);
			h[i]=(h[i-1]*bas+id)%mod;
		}
		……
		……
		……

strlen所作的仅仅是一个计数器的工作,它从内存的某个位置(可以是字符串开头,中间某个位置,甚至是某个不确定的内存区域)开始扫描,直到碰到第一个字符串结束符’\0’为止,然后返回计数器值(长度不包含’\0’)。

我们用cin>>s和scanf(%s)输入字符串时,会在接受完字符串后在末尾加个’\0’,也就是NULL作为字符串结束符。

所以用strlen函数求解字符串长度的时候,复杂度就是O(n)。如果在循环中用

for (int i = 0; i < strlen(s); ++i)//复杂度O(n2) 那就麻烦了,可能会超时,解决办法:

(1)先把strlen(s)先用变量存储起来,再放入循环中;

(2)或者用s[i] != ‘\0’;

问题二

1、
ll mod[2]={1e9+7,999999998};编译器上运行没问题,交到codeforce上,compile error,理由是1e9+7将算作double类型的浮点数, 1.000000000 ∗ 1 0 7 1.000000000*10^7 1.000000000107
ll mod[2]={1000000007,999999998};✔
2、类似的还有,cin>>s+1,报错说是没有重载运算符啥的operator
3、还有之前的next的重名

问题三

双重哈希或者多重哈希,一定要每一种哈希函数之下的哈希值都相等才能判断两字符串相等,是个&&的关系,所以

st=len+1;
int pos=0;
ll H[2]={0,0};
for(int i=1;i<=le&&i<=len;i++){
			int id=getID(s[i]);
			int ok=1;
			for(int k=0;k<2;k++){
			H[k]=(H[k]*bas[k]+id)%mod[k];//s字符串s[1--i]这段哈希值
//	ll temp=((h[k][len]-h[k][len-i]*p[k][i])%mod[k]+mod[k])%mod[k];
			if(H[k]!=getH(len-i+1,len,k))ok=0;
			}
			if(ok)pos=i;//12345
		}

只要在一个哈希函数下的哈希值对应不相等,就不能算作字符串相等

问题四

H[2][N]优化为H[2]嘛,算是个临时变量,专门用来记录输入下一个单词的前缀哈希值,不断输入单词,所以H数组每次都要记得初始化啊,不能在全局变量定义了就完事了(只初始化一次),因为==H[k]=(H[k]*bas[k]+id)%mod[k];==要用到上次的H【k】值,有点像滚动数组的优化
如果不用滚动数组优化,就不用次次初始化啦,
H[k][i]=(H[k][i-1]*bas[k]+id)%mod[k];
耗空间呀

	for(int i=1;i<=le&&i<=len;i++){
			int id=getID(s[i]);
			int ok=1;
			for(int k=0;k<2;k++){
			H[k][i]=(H[k][i-1]*bas[k]+id)%mod[k];//s字符串s[1--i]这段哈希值
//			ll temp=((h[k][len]-h[k][len-i]*p[k][i])%mod[k]+mod[k])%mod[k];
			if(H[k][i]!=getH(len-i+1,len,k))ok=0;
			}
			if(ok)pos=i;//12345
		}

问题五

用字符串hash可以O ( n ) 处理,O ( 1 )比较。因为每个串只会被扫一遍,可以直接暴力,预处理 O ( n ) ,暴力O ( n ) O(n)查找最长相同部分。

由于字符串长达到了 1 0 6 10^6 106
单模hash 已经搞不了,要用更安全的双 hash,取 2 个较大的模数 mod1 和 mod2,以及一个素数 p (p 不用太大,单模的 p 也不用太大,模数一定要大)

ll bas[2]={131,2333333};
ll mod[2]={1000000007,999999998};

1e5可以用 P取131,mod取1<<64,通过unsigned long long来取模的 组合 单哈希,
一旦到了1e6还是老老实实用双哈希甚至三哈希,其实也不是什么难事儿,把p数组,mod数组,H数组,h数组,都变成k维就好,多一个循环for(int j=0;j<k;k++)……就好

建议取以上两种哈希函数的P和mod组合,很强,后面那一组用来单哈希在这题甚至可以AC

问题六

这题大小写字母是当作不相等的,一开始理解反了,白费我写一个
int getID(char ch){//65 97
if(ch>=‘A’&&ch<=‘Z’)ch+=32;
return ch;
}
这样可,但没必要,P进制P取得已经够大了,48——65——97——122,完全可以错开取不同值,直接用s[i]就ok
int getID(char c){
if(c>=‘a’&&c<=‘z’) return c-‘a’;
if(c>=‘0’&&c<=‘9’) return c-‘0’+26;
return c-‘A’+36;
}

问题七

复杂度分析的不好,一开始超时还以为是快速幂和初始化P的幂次的初始化的选择问题
测试证明,这题初始化P幂次的数组p来得比快速幂快
反正 初始化P幂次的数组p 就是O(n)的复杂度,不会造成实质性的超时,用这个

代码

单哈希
2333333
999999998
就连131和unsigned long long的组合也会被卡

#include <iostream>
#include <string.h>
using namespace std;
typedef long long ll;
const int N=1e6+10;
char t[N];
char s[N];
ll bas=2333333;
ll mod=999999998;
ll h[N];
ll H[N];
ll p[N];
void init(){
	p[0]=1;
	for(int i=1;i<N;i++){//N位最高幂次是N-1
		p[i]=(p[i-1]*bas)%mod;
	}
}
ll getH(int l,int r){
	return ((h[r]-h[l-1]*p[r-l+1])%mod+mod)%mod;
}
int main(){
	init();
	int n;
	cin>>n;
	scanf("%s",t+1);
	int len=strlen(t+1);
	int st=1;
	for(int j=1;j<n;j++){
		scanf("%s",s+1);
		int le=strlen(s+1);
		for(int i=st;i<=len;i++){
			h[i]=(h[i-1]*bas+t[i])%mod;
		}
		st=len+1;
		int pos=0;
		for(int i=1;i<=le&&i<=len;i++){
			H[i]=(H[i-1]*bas+s[i])%mod;//s字符串s[1--i]这段哈希值
			if(H[i]==getH(len-i+1,len))pos=i;//12345
		}
		for(int i=pos+1;i<=le;i++){
			t[++len]=s[i];
		}
	}
	printf("%s",t+1);
	return 0;
}

双哈希(So easy,先写个单哈希,再逐一将 m o d − > m o d [ k ] , b a s − > b a s [ k ] , h [ i ] − > h [ k ] [ i ] mod->mod[k],bas->bas[k],h[i]->h[k][i] mod>mod[k],bas>bas[k],h[i]>h[k][i],求哈希值时用个k循环,要注意的就是判断字符串相等时一定要每个哈希函数下的哈希值都满足条件

用二维数组比写两个数组好多了,还可以根据编译器报错的地方补k

#include <iostream>
#include <string.h>
using namespace std;
typedef long long ll;
const int N=1e6+10;
char t[N];
char s[N];
ll bas[2]={131,2333333};
ll mod[2]={1000000007,999999998};
ll h[2][N];
ll H[2][N];
ll p[2][N];
void init(){
	p[0][0]=p[1][0]=1;
	for(int i=1;i<N;i++){//N位最高幂次是N-1
		for(int k=0;k<2;k++)
		p[k][i]=(p[k][i-1]*bas[k])%mod[k];
	}
}
ll getH(int l,int r,int k){
	return ((h[k][r]-h[k][l-1]*p[k][r-l+1])%mod[k]+mod[k])%mod[k];
}
int main(){
	init();
	int n;
	cin>>n;
	scanf("%s",t+1);
	int len=strlen(t+1);
	int st=1;
	for(int j=1;j<n;j++){
		scanf("%s",s+1);
		int le=strlen(s+1);
		for(int i=st;i<=len;i++){
			for(int k=0;k<2;k++)
			h[k][i]=(h[k][i-1]*bas[k]+t[i])%mod[k];
		}
		st=len+1;
		int pos=0;
		for(int i=1;i<=le&&i<=len;i++){
			int ok=1;
			for(int k=0;k<2;k++){
			H[k][i]=(H[k][i-1]*bas[k]+s[i])%mod[k];//s字符串s[1--i]这段哈希值
			if(H[k][i]!=getH(len-i+1,len,k))ok=0;
			}
			if(ok)pos=i;//12345
		}
		for(int i=pos+1;i<=le;i++){
			t[++len]=s[i];
		}
	}
	printf("%s",t+1);
	return 0;
}

最后还可以用滚动数组的思想优化一下空间
H[2][N]–>H[2]

	ll H[2]={0,0};
		for(int i=1;i<=le&&i<=len;i++){
			int ok=1;
			for(int k=0;k<2;k++){
			H[k]=(H[k]*bas[k]+s[i])%mod[k];//s字符串s[1--i]这段哈希值
			if(H[k]!=getH(len-i+1,len,k))ok=0;
			}
			if(ok)pos=i;//12345
		}

反过来求哈希值,搞清楚各位的权值

……cba cba……
<—— ——>
求abc
反过来求反过来字符串的哈希值(高位到低位),和原来一样
h = h ∗ b a s + a n s [ i ] h=h*bas+ans[i] h=hbas+ans[i]
正着顺序求反过来字符串的哈希值(低位到高位),
H + = H ∗ w e i g h t — — w e i g h t ( 11 ∗ b a s 1 ∗ b a s ∗ b a s ) H+=H * weight ——weight(1 1*bas 1*bas*bas) H+=Hweightweight(11bas1basbas)
由低位到高位求数值 各位权值为1 1bas 1bas*bas

s . s i z e ( ) 比 s t r l e n ( s ) 快 好 多 s.size()比strlen(s)快好多 s.size()strlen(s)

在这里插入图片描述

中间是倒着求字符串哈希值,用ans.size()
最顶上是把ans.size()换成strlen(ans)
最底下是开了数组双哈希的情形

#include <iostream>
#include <string.h>
using namespace std;
typedef unsigned long long ull;
const int N=1e6+10;
//ll bas[2]={131,2333333};
//ll mod[2]={1000000007,999999998};

char ans[N];
char s[N];
//string ans,s;
ull bas=2333333;
ull mod=999999998;
int main(){
	int n;
	cin>>n;
	cin>>ans;
	for(int j=1;j<n;j++){
		cin>>s;
		int len=strlen(ans);
		int le=strlen(s);
		ull bass=1;
		int pos=0;
		ull h=0;//一定每次要初始化的呀,只要不是通过数组记录每一段的哈希值
//		而是一个变量记录某一段的哈希值一定要惊醒初始化 
		ull H=0;
		for(int i=1;i<=min(len,le);i++){//枚举长度cdefabc  abcmnjk
			h=(h*bas+ans[len-i])%mod;//求字符串反过来的哈希值 cba
			H=(H+s[i-1]*bass)%mod;
//由于倒过来,由低位到高位求数值 各位权值为1 1*bas 1*bas*bas 
			bass=bass*bas%mod;
			if(H==h)pos=i;//i其实是相等字符的下一个的下标了 
		}
//		ans+=substr(pos);//如果是string ans; ans[len++]是不行的 
		for(int i=pos;i<le;i++){
			ans[len++]=s[i];
		} 
	}
	cout<<ans;
	return 0;
}
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: `org/apache/commons/compress/utils/InputStreamStatistics` 类通常是由 Apache Commons Compress 库提供的。因此,您需要将以下依赖项添加到您的项目中,以解决这个问题: Maven 项目: ```xml <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-compress</artifactId> <version>1.21</version> </dependency> ``` Gradle 项目: ```groovy implementation 'org.apache.commons:commons-compress:1.21' ``` 请注意,版本号可能会随着时间的推移而有所更改,因此请根据情况更新版本号。 ### 回答2: org/apache/commons/compress/utils/InputStreamStatistics 对应的依赖是 Apache Commons Compress 库。 Apache Commons Compress 是一个流行的开源 Java 库,用于处理各种压缩和存档格式。它提供了一系列的类和工具,用于读取、写入和处理各种压缩文件,如ZIP、GZIP、7z、Tar等。 在 Apache Commons Compress 库中,org/apache/commons/compress/utils/InputStreamStatistics 是一个工具类,用于统计输入流的读取信息。它提供了方法来跟踪和报告输入流读取的字节数、读取的块数以及读取速度等信息。这可以帮助开发人员监控和优化输入流的读取效率,特别是在处理大量数据时。 要在项目中使用 org/apache/commons/compress/utils/InputStreamStatistics 类,需要在项目的构建文件中添加对 Apache Commons Compress 库的依赖。具体的依赖配置方法取决于项目使用的构建工具,如 Maven、Gradle等。 以下是使用 Maven 作为构建工具时添加依赖的示例: ```xml <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-compress</artifactId> <version>1.21</version> </dependency> ``` 这样就可以在项目中使用 org/apache/commons/compress/utils/InputStreamStatistics 类,并享受它提供的统计功能了。记得根据实际需求和项目的特定版本需求,调整所使用的 Apache Commons Compress 库的版本号。 ### 回答3: org/apache/commons/compress/utils/InputStreamStatistics对应的依赖是commons-compress库。commons-compress是一个用于处理压缩和解压缩的Java库,提供了对不同压缩格式(如zip、tar、gzip等)的读取和写入功能。InputStreamStatistics是该库中的一个工具类,用于统计输入流的读取数据量、读取次数和平均速度等信息。 要使用org/apache/commons/compress/utils/InputStreamStatistics,需要在项目的构建文件(如pom.xml)中添加对commons-compress库的依赖声明。例如,使用Maven构建项目,可以在pom.xml文件中添加以下依赖: ```xml <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-compress</artifactId> <version>版本号</version> </dependency> ``` 其中,版本号是需要指定的commons-compress库的版本号。 添加了对commons-compress库的依赖后,就可以在项目中使用org/apache/commons/compress/utils/InputStreamStatistics。通过创建InputStreamStatistics对象,可以对输入流的读取进行统计,并获取各种读取信息。使用InputStreamStatistics可以方便地了解输入流的读取情况,帮助开发者进行性能优化和资源管理。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值