数字串(权值思想和字符串哈希)

码题集OJ-数字串 (matiji.net)

权值思想以及字符串哈希是比较难想到的点。首先我们考虑加号放在哪里会使相加的和最大,我们不妨假设是一个大的字符串加上的都是单个的数最大。比如abcde,我们要放置两个加号,我们假设a+bc+de最大,那么我们可以想一想会不会有比它更大的,最大字符串位数是三位数的。我们可以举一个例子,a+b+cde,我们把两个数相减,a+b+cde-(a+bc+de)=c*100+b-b*10-c=99c-9b>0,所以无论我们认为哪一个字符串分割之后可以是多个不是单个字符的数最大,我们总能找到一个符合条件的更大的。

接下来可以假设另一个结论,找到的主体字符串(+分割之后位数最大的字符串)最大,那么分割之后相加一定最大。例如abcde,我们假设有两种分法abcd+e和a+bcde,那么两式相减=abcd-bcda。假如abcd>bcde,那么一定有四种情况

1.a>b那么第一个式子大

2.a=b,b>c,第一个式子大

3.a=b,b=c,c>d,第一个式子大

4.a=b,b=c,c=d,d>e,两式相等

如果相等那么a=b=c=d=e,怎样分割都是一样的。

所以我们问题就转化为了寻找最大的主体串。因为数据量太大,我们不能用int和longlong存,只能借助字符串哈希来寻找最大主体串。

#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
typedef long long LL;
const int N=1e6+5,R=60,MOD=1e8+7;
int n,k;
string s;
LL hs[N],pr[N];
vector<int>res;
//两个vector的和存到res中
void addTwoVector(vector<int>&a,vector<int>&b) {
	if(a.size()>b.size())return addTwoVector(b,a);
	int t=0;
	for(int i=0; i<a.size(); i++) {
		t+=a[i]+b[i];
		res.push_back(t%10);
		t/=10;
	}
	for(int i=a.size();i<b.size();i++){
		t+=b[i];
		res.push_back(t%10);
		t/=10;
	}
	if(t)res.push_back(t);
}
//获取从下标l开始r结束的哈希值
int get(int l,int r) {
	return (hs[r]-(LL)hs[l-1]*pr[r-l+1]%MOD+MOD)%MOD;
}
//寻找从下标x开始的n-k个数与从下标y开始的n-k个数的最长公共前缀的长度
int lcp(int x,int y){
	int l=0,r=n-k-1;
	while(l<r){
		int mid=l+r+1>>1;
		if(get(x,x+mid)==get(y,y+mid)){
			l=mid;
		}else{
			r=mid-1;
		}
	}
	return get(x,x+l)==get(y,y+l)?l+1:0;
}
int main(void) {
	scanf("%d%d",&n,&k);
	cin>>s;
	s=" "+s;
	pr[0]=1;
	//创建前缀和,因为每个位数上的最大值是'9'的ASCII对应十进制57
	//所以我们进制可以设为60>57;
	for(int i=1; i<=n; i++) {
		hs[i]=(hs[i-1]*R%MOD+s[i])%MOD;
		pr[i]=pr[i-1]*R%MOD;
	}
	int maxi=1;
	//根据前缀和来计算从i开始的n-k位数的最大值
	for(int i=1; i<=k+1; i++) {
		int len=lcp(maxi,i);
		if(len==n-k)continue;
		maxi=(s[maxi+len]>s[i+len])?maxi:i;
	}
	vector<int>mainBody,single;
	for(int i=maxi+n-k-1;i>=maxi; i--) {
		mainBody.push_back(s[i]-'0');
	}
	int w=0;
	for(int i=1; i<=maxi-1; i++) {
		w+=s[i]-'0';
	}
	for(int i=maxi+n-k; i<=n; i++) {
		w+=s[i]-'0';
	}
	while(w){
		single.push_back(w%10);
		w/=10;
	}
	addTwoVector(mainBody,single);
	for(int i=res.size()-1; i>=0; i--) {
		printf("%d",res[i]);
	}
	return 0;
}

首先,程序使用了一些头文件和命名空间,并定义了一些常量和变量。其中,N表示字符串的最大长度,R表示用于计算哈希值的进制,MOD表示取模的值,nk分别表示输入的字符串长度和需要寻找的子串长度,s表示输入的字符串,hspr是用于计算哈希值的数组,res是一个vector,用于存储两个vector的和。

接下来,程序定义了两个函数:

  1. addTwoVector函数用于将两个vector的和存到res中。
  2. get函数用于获取从下标l开始r结束的哈希值。
  3. lcp函数用于寻找从下标x开始的n-k个数与从下标y开始的n-k个数的最长公共前缀的长度。

main函数中,程序首先读入输入的字符串长度和需要寻找的子串长度。然后,读入字符串并对其进行处理,将字符串的第一个字符设为空格。接着,程序创建前缀和,并计算从i开始的n-k位数的最大值。然后,程序将最大值存储到mainBody vector中,并将前缀和后缀的值存储到single vector中。最后,程序调用addTwoVector函数将两个vector的和存到res中,并输出结果。

总体来说,这段代码使用哈希值和前缀和的方法寻找一个字符串中长度为n-k的最大子串,并将其输出。

  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值