P7861 SAVEZ 题解

本文详细解析了如何利用哈希技巧解决一道寻找最长子序列的问题,其中子序列需满足后续字符串以先前字符串结尾。通过计算字符串开头和结尾的哈希值,并用映射记录最长序列长度,最终找到最长子序列。代码实现中采用了双哈希避免哈希冲突,但单哈希同样有效。

P7861 SAVEZ 题解(哈希)

思路

题目中要求我们找出子序列 xxx 使得 xxx 最长且满足其中靠后的字符串都以它之前的字符串作为结尾,即下图所示:

图片

我们需要比较字符串的开头和结尾是否相等且在之前出现过,因此我们使用哈希将开头和结尾存起来。

由于我们要的并不是回文串,所以我们需要将开头与结尾的哈希值用不同的方法分别求。

对于在开头的部分:hash=(hash+hashpowj×trj)hash = (hash+hashpow_j\times tr_j)%modhash=(hash+hashpowj×trj),其中 hashpowjhashpow_jhashpowj 表示进制数的 jjj 次方。

对于在结尾的部分:hash=(hash×hashnum+strj)hash=(hash\times hashnum+str_j)%modhash=(hash×hashnum+strj),其中 hashnumhashnumhashnum 表示进制数。

接下来,为了确定开头和结尾的字符串在之前有没有出现过,我们用一个 map 存一下每个哈希值对应的最长的序列长度 dphashdp_{hash}dphash。然后再取最大的 dphashdp_{hash}dphash+1+1+1 就好啦!

我的代码用了双哈希避免哈希碰撞,但实测单哈希也能过。

代码

#include<bits/stdc++.h>
using namespace std;
const int hash_code1=114;
const int hash_code2=514;//太臭力!
const int mod=1000000007;
long long hasheachnum1[1000006];
long long hasheachnum2[1000006];
void geth()
{
	hasheachnum1[0]=1;
	hasheachnum2[0]=1;
	for(int i=1;i<1000006;i++)//求hashpow[i]
	{
		hasheachnum1[i]=(hasheachnum1[i-1]*hash_code1)%mod;
		hasheachnum2[i]=(hasheachnum2[i-1]*hash_code2)%mod;
	}
	return;
}
int main()
{
	geth();
    string loda;
	int N,ans=0;
	scanf("%d\n",&N);
	map<pair<int,int>,int> dp;//经过实测,map里面不能塞struct作为下标,所以用pair存双哈希
	for(int i=1;i<=N;i++)
	{
		cin>>loda;
		long long hashx1=0,hashx2=0;//开头的哈希
		long long hashy1=0,hashy2=0;//结尾的哈希
		int l=loda.size(),temp=0;
		for(int j=0;j<l;j++)
		{
			hashx1=(hashx1+hasheachnum1[j]*(int)loda[j])%mod;
			hashx2=(hashx2+hasheachnum2[j]*(int)loda[j])%mod;
			hashy1=(hashy1*hash_code1+(int)loda[l-j-1])%mod;
			hashy2=(hashy2*hash_code2+(int)loda[l-j-1])%mod;
			if(hashx1==hashy1 && hashx2==hashy2 && temp<dp[{hashx1,hashx2}])//如果前后的哈希值相等就更新dp值
				temp=dp[{hashx1,hashx2}];
		}
		dp[{hashx1,hashx2}]=temp+1;
		if(temp>=ans)
			ans=temp+1;
	}
	printf("%d\n",ans);
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值