2020牛客暑期多校训练营All with Pairs(Hash,前缀函数)

All with Pairs

题目描述

在这里插入图片描述

输入描述:

在这里插入图片描述

输出描述:

在这里插入图片描述

示例1

输入

3
ab
ba
aba

输出

29

说明

在这里插入图片描述

题目大意

给定n个字符串,求所有字符串前缀与后缀相等的个数与前后缀的长度的平方的和。如样例,匹配长度为1,2,3的分别有4,4,1个,所以答案为
4 *1 ^2 +4 *2 ^2 +1 *3 ^2=29

分析

许多dalao们看到这题就会想到用map把所有的后缀(前缀)映射进去,然后对于每个前缀(后缀),就可以直接查找下就可以了。但是,map是pair和set合用,这全搞在一起是很慢的,看到1e6的长度,就是TLE。所以要找一个更加高深的算法。

HASH

哈希是字符串题目中的bug方法,基本上能水过去,实在不行还能骗不少分。本题中可以把后缀(前缀)的哈希值求出,然后将前缀(后缀)的哈希值与之比较,就能比较快捷地求出匹配了。但是哈希的进制搞成多少好呢?一般我们都是取一个素数进制,冲突的概率比较小。然而,本题有1e6的长度,以233进制的哈希为例,搞不好就是2331e6,炸上天。因此需要一个mod。但是这个mod之后,它也有概率会冲突。这就是看运气了。有一下3种比较好的解决这个办法。(后文采取第二种)
1、用大素数取模,这种冲突产生的概率就十分小了。
2、用unsigned long long,直接省略超出部分,概率小且写代码方便。
3、双哈希,这种方式就是取两种进制,然后都取一个mod,如果这都能冲突,那真可以买彩票了,唯一不足就是有点烦,蒟蒻不太喜欢这种写法。

以下是图示,是哈希的原理,即为进制转换,转换成233进制之类的。第一位的数值是2330 *当前位,以此类推。
在这里插入图片描述
具体代码见下。

void puthash(string a){
    int len=a.size();
    ull sum=0,p=1;//用ull解决冲突
    for(int i=len-1;i>=0;i--){
        sum+=(a[i]-'a'+1)*p;
        p*=233;
        mp[sum]++;
    }//存后缀信息
}

KMP

如果你觉得这样就可以AC的话,那么你会喜提WA一枚。
来看下面这种情况。
在这里插入图片描述
如图,2,3串的匹配,是莫得问题的,然而,1串它先匹配了’a’,然后在判前缀为’aba’时又配了一次。但事实上,它只能匹配一次’aba’,所以要用到KMP里面的前缀函数nxt,快速找到最长的匹配串,然后把短的舍去。

具体写法如下(其实就是普通的KMP里的nxt

void getnxt(string w){
    int len=w.size(),k=-1;
    nxt[0]=-1;
    for(int i=1;i<len;i++){
        while(k>-1&&w[k+1]!=w[i]) k=nxt[k];
        if(w[k+1]==w[i]) k++;
        nxt[i]=k;
    }
}

代码

#include<bits/stdc++.h>
#define ll long long
#define inf 1<<30
#define ull  unsigned long long
using namespace std;
const int M=1e5+100;
const int N=1e6+100;
const ull mod=998244353;
string s[M];//本来用了char,但是不用string的动态存储,这个会爆空间啊
int nxt[N],cur[N];
map<unsigned long long,int> mp;
void getnxt(string w){
    int len=w.size(),k=-1;
    nxt[0]=-1;
    for(int i=1;i<len;i++){
        while(k>-1&&w[k+1]!=w[i]) k=nxt[k];
        if(w[k+1]==w[i]) k++;
        nxt[i]=k;
    }
}
void puthash(string a){
    int len=a.size();
    ull sum=0,p=1;
    for(int i=len-1;i>=0;i--){
        sum+=(a[i]-'a'+1)*p;
        p*=233;mp[sum]++;
    }
}
int main()
{
    int n;
    ll ans=0;
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>s[i],puthash(s[i]);//存入后缀
    for(int i=1;i<=n;i++){
        int len=s[i].size();
        ull t=0;
        for(int j=0;j<len;j++){
            t=t*233+(s[i][j]-'a'+1);
            cur[j]=mp[t];
        }//存入前缀信息
        getnxt(s[i]);//得到前缀函数
        for(int j=0;j<len;j++)
            if(nxt[j]>=0) cur[nxt[j]]-=cur[j];//若有更长的的可以替换,则果断减掉
        for(int j=0;j<len;j++){
            ans+=cur[j]%mod*(j+1)%mod*(j+1)%mod;
            ans%=mod;//按照题意存入答案,并mod
        }
    }
    cout<<ans<<endl;
}

END

猜你喜欢

转载自blog.csdn.net/zhangchizc/article/details/107367650