题意:H(s)=∏i≤len(s)i=1(Si−28) (mod 9973),求一个字符串 子串(a 位到 b 位的)的哈希值。这个公式便是求字符串哈希值的公式,(字符的哈希值 = 字符的ASCII码 - 28),字符串的哈希值等于字符的哈希值的乘积( ∏ 这个就是累乘符号 )。
化简过后的题意就是求一段序列中的区间乘,由于询问次数比较多,直接求乘会超时。
就比如如下代码:
#include<stdio.h>
char s[100010];
int main()
{
int T,a,b;
while(~scanf("%d",&T))
{
int ans = 1;
scanf("%s",s+1);
while(T--){
ans =1;
scanf("%d%d",&a,&b);
for(int i=a;i<=b;i++)
ans = ans * (s[i] - 28 ) % 9973;
printf("%d\n",ans);
}
}
return 0;
}
所以用前缀乘的方法,例如:0到b 区间的哈希值,除以 0到a 区间的哈希值,得到的就是 a 到 b 的哈希值。
若用preMulit[i]表示前i个序列的前缀乘。要求[l,r]区间内的的数字全乘起来,那么用preMulit[r] / preMulit[l-1] 即可,考虑到取模的问题,用到逆元。
那么我就要问了,为什么考虑到取模的问题,就要用到逆元呢?而且什么是逆元呢?
(看链接)
接下来就是解题代码了
代码一:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll maxn=100005;
const ll mod=9973;
ll sum[maxn],inv[maxn],re[maxn];
char s[maxn];
int main()
{
inv[1]=1;
for(int i=2;i<maxn;++i)
{
inv[i]=inv[mod%i]*(mod-mod/i)%mod;
}
int n;
//freopen("shuju.txt","r",stdin);
while(~scanf("%d",&n))
{
scanf("%s",s+1);
sum[0]=re[0]=1;
for(int i=1;s[i]!=0;++i)
{
sum[i]=(sum[i-1]*(s[i]-28))%mod;
re[i]=inv[sum[i]];
}
for(int i=0;i<n;++i)
{
ll a,b;
scanf("%lld%lld",&a,&b);
ll tp=re[a-1];
printf("%I64d\n",(sum[b]*tp)%mod);
}
}
return 0;
}
代码二:(通过extgcd直接求)
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll maxn=100005;
const ll mod=9973;
ll sum[maxn];
char s[maxn];
void extgcd(ll a,ll b,ll &x,ll &y)
{
if(!b)
{
x=1;y=0;
return;
}
extgcd(b,a%b,y,x);
y-=(a/b)*x;
}
ll inv(ll a,ll n)
{
ll d,x,y;
extgcd(a,n,x,y);
return (x+n)%n;
}
int main()
{
int n;
//freopen("shuju.txt","r",stdin);
while(~scanf("%d",&n))
{
scanf("%s",s+1);
sum[0]=1;
for(int i=1;s[i]!=0;++i)
{
sum[i]=(sum[i-1]*(s[i]-28))%mod;
}
for(int i=0;i<n;++i)
{
ll a,b;
scanf("%lld%lld",&a,&b);
ll tp=inv(sum[a-1],mod);
printf("%I64d\n",(sum[b]*tp)%mod);
}
}
return 0;
}