2019 ICPC上海网络赛 G. Substring 哈希+尺取法+unordered_map

题目链接:https://nanti.jisuanke.com/t/41415

赛后补题。

参考博客:https://blog.csdn.net/bjfu170203101/article/details/100889468

题意:给出一个主串(假设长度为m),再给出n个模式串,对于每一个模式串,如果在主串中有一个子串,它的第一个字符和最后一个字符分别与这个模式串的开头字符和结尾字符相同,并且每一种字符出现的次数相同,那么就把这个模式串和这个子串看成是相同的,认为模式串在主串中出现过一次,比如模式串是:abccd,那么主串中如果有子串accbd或者acbcd这些,都可以认为模式串和子串是相同的,但是对于dbcca,accd这些,则是认为不相同的。

现在我们需要计算出每一个模式串在主串中出现的次数。

思路:对每一个模式串求它的哈希值,把所有子串中出现的哈希值都放在unordered_map中,我们可以用尺取法在O(n)的时间复杂度中遍历一次模式串,找所有长度都为len的模式串在主串中出现的次数,有多少种不同的长度就遍历主串多少次,这样看起来貌似时间复杂度最坏是O(n*m),但是这道题有一个条件就是所有模式串的长度之和不会超过1e5,那么就说明最多只会有大约sqrt(2e5)个不同的长度,因为1+2+......+n=n*(1+n)/2=1e5。

这样实际上我们最多只需要遍历sqrt(2e5)次主串就行了,时间复杂度是O(sqrt(2e5)n)。

这里面我们还需要找一个构造哈希值的方法,使得所有首尾字符相同,并且中间各中字符出现次数相同的串的哈希值一定相同,并且还需要尽量降低哈希冲突的概率(降低什么的我也不太懂)。

因为除去首尾字符中间出现的字符只关心出现次数,不关心出现次序,所以选择用加法,对于每个出现的字符,我都加上它的Pow(base,s[i]-‘a‘+1),我这里base是131,假如s[i]=‘a‘,那么就是加上131^1,s[i]=‘b‘就加上131^2,以此类推。这个base一般是一个质数,这样我们就得到了模式串除去首尾字符的哈希值(假设是value),然后我把value=value*10000,然后再

value+=s[0]-‘a‘+1,value+=(s[len-1]-‘a‘+1)*100,实际上就是将模式串的首字符经过处理后的值放在个位和十位,将尾部字符经过处理后的值放在百位和千位,这样我们就可以保证首尾字符相同,并且中间各种字符出现次数相同的字符串的哈希值一定是相同的。

对于子串abac,去掉首尾字符就是ba,value=base^2+base^1=17292。value=value*10000=172920000,把首尾加进去就是172920301,

03是尾部字符,01是首部字符。

同时我们在求主串中某个长度的所有子串的哈希值的时候不需要每次都重新求他们的哈希值,可以用尺取法根据前后之间的联系在O(1)的时间中算出向右移动一个单位的哈希值。

我们使用unordered_map,好像比使用map少一个logn的时间复杂度(貌似,好像,也许,可能)。

代码:

#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<map>
#include<tr1/unordered_map>
#include<stack>
#include<cmath>
#include<vector>
#include<set>
#include<cstdio>
#include<string>
#include<deque>
using namespace std;
using namespace std::tr1;
typedef unsigned long long ll;
#define eps 1e-8
#define INF 0x3f3f3f3f
#define maxn 100005
const int base=131;//一个质数
const ll mod=100280245065;//一个12位的质数
ll f[30];
char s[maxn],ss[maxn];
unordered_map<ll,int>mp;
ll value[maxn];
int len[maxn];
int n,m,k,t,cnt;
ll Pow(ll a,ll b){
    ll ans=1;
    while(b){
        if(b&1)
        ans=(ans*a)%mod;
        a=(a*a)%mod;
        b>>=1;
    }
    return ans;
}
ll getHash(char *s,int l,int r){//求字符串s在区间[l,r]中的子串的哈希值
    ll ans=0;
    for(int i=l+1;i<=r-1;i++){//求去掉首尾字符的值
        ans=(ans+f[s[i]-‘a‘+1])%mod;
    }
    ans*=10000;//将ans往前挪4个十进制位置
    ans+=s[l]-‘a‘+1;//子串中首字符的值放在个位和十位中
    ans+=(s[r]-‘a‘+1)*100;//子串中的尾部字符的值放在百位和千位中
    return ans;
}
int main()
{
    scanf("%d",&t);
    f[0]=1;
    for(int i=1;i<30;i++)
    f[i]=(f[i-1]*base)%mod;//f[i]就是(base^i)%mod
    while(t--){
        mp.clear();
        scanf("%s",s);
        m=strlen(s);//m是主串的长度
        scanf("%d",&n);
        cnt=0;
        for(int i=0;i<n;i++){
            scanf("%s",ss);
            len[i]=strlen(ss);
            value[i]=getHash(ss,0,len[i]-1);//得到当前字符串的哈希值
            mp[value[i]]=1;//当前字符串出现过
        }
        sort(len,len+n);
        int size=unique(len,len+n)-len;//去重,得到不同长度的个数
        for(int i=0;i<size;i++){
            ll now;
            for(int j=0;j+len[i]-1<m;j++){
                if(j==0)
                now=getHash(s,j,j+len[i]-1);
                else{//因为是尺取法,所以可以不用重新求哈希值,只需要左右边界处理一下就行了
                    now/=10000;//去掉前一个子串两边的字符的值
                    if(len[i]>2)//判断一下长度是否大于2
                    now=(now-f[s[j]-‘a‘+1]+f[s[j+len[i]-2]-‘a‘+1]+2*mod)%mod;//从区间[j-1,j+len[i]-2]的除去首尾字符now推出区间[j,j+len[i]-1]除去首尾字符的now
                    now*=10000;
                    now+=s[j]-‘a‘+1;//重新加上首尾字符处理后的值
                    now+=(s[j+len[i]-1]-‘a‘+1)*100;
                }
                if(mp.find(now)!=mp.end()){//判断主串中是否有这个模式串
                    mp[now]++;
                }
            }
        }
        for(int i=0;i<n;i++)
        printf("%d\n",mp[value[i]]-1);
    }
    return 0;
}

原文地址:https://www.cnblogs.com/6262369sss/p/11537356.html

时间: 2024-08-30 07:46:17

2019 ICPC上海网络赛 G. Substring 哈希+尺取法+unordered_map的相关文章

2019 ICPC 南昌网络赛

2019 ICPC 南昌网络赛 比赛时间:2019.9.8 比赛链接:The 2019 Asia Nanchang First Round Online Programming Contest 总结 // 史上排名最高一次,开场不到两小时队友各A一题加水题共四题,排名瞬间升至三四十名 // 然后后三小时就自闭了,一题都没有突破...最后排名211 hhhh ? ? B. Fire-Fighting Hero 题意 队友做的,待补. ? AC代码 #include<cstdio> #includ

2019 上海网络赛G 手写哈希map+字符串hash

https://nanti.jisuanke.com/t/41415 因为对于询问$\sum |s|<=1e5$,因此$|s|$的种类数$<=\sqrt{1e5}$ 我们分组标记,就变成了$\sqrt{1e5}$次询问了,我们暴力去跑, 复杂度$1e5\sqrt{1e5}*hashmap$ 暴力+哈希+$unordermap$计数,嗯,果然TLE了 然后手写一个$hashmap$,跑的飞快... #include<bits/stdc++.h> #define ull unsigne

2019 ACM-ICPC 上海网络赛 B. Light bulbs (差分)

题目链接:Light bulbs 比赛链接:The Preliminary Contest for ICPC Asia Shanghai 2019 题意 给定 \(N\) 个灯泡 (编号从 \(0\) 到 \(N - 1\)),初始都是关闭的. 给定 \(M\) 个操作,每个操作包含 \(L\) 和 \(R\),对 \([L, R]\) 内的所有灯泡改变状态. 求最后有几个灯泡是亮的. 思路 题目挺简单的,翻转奇数次的灯泡是亮的,所以要求每个灯泡翻转的次数. 容易想到可以用差分. 对所有操作的两

2019 ICPC 南昌网络赛 - Subsequence (子串判断,预处理,超时)

?? ?? ?? 题意:一个字符串s,t个字符串p,判断p是不是s的子序列 预处理出对于字符串s,每个位置i之后第一个字母x的位置,匹配子序列的时候就相当于在母串上往后跳,如果跳到母串末尾,子序列还没有结束,匹配失败. 然而!超时了QAQ,只能过八组样例,网上也只能找到过九组的,,,希望有哪位大神路过指点一下了 int tag[33],nxt[MAXN][30]; char s[MAXN],p[MAXN]; void init() { int n=strlen(s); for(int j=0;j

2019 ICPC 南昌网络赛 - Max answer (区间和,区间最值)

?? ???? 题意:价值 = 区间和 × 区间最小值,求数组的子区间的最大价值 (1)区间和---->前缀和 (2)O(n^2) 枚举区间 ---> O( n ) 枚举元素,根据当前元素查询相应区间和 对每个元素,维护他作为最小值的左右端点,枚举数组中的元素,该元素大于0为例,查找( i , r [ i ] )的前缀和最大值,( l [ i ] - 1 , i - 1 )的前缀和最小值,注意这里 l [ i ] -1 可能会小于1 ,ST表应从0开始维护 丧心病狂的压行QWQ ll a[MA

2018 ICPC 徐州网络赛

2018 ICPC 徐州网络赛 A. Hard to prepare 题目描述:\(n\)个数围成一个环,每个数是\(0\)~\(2^k-1\),相邻两个数的同或值不为零,问方案数. solution 将环变成链,设\(f[i][0\)~\(2]\),分别表示与第一个数相同,与第一个数不同,与第一个数相同,与第一个数的反相同.然后\(dp\)即可. 时间复杂度:\(O(n)\) B. BE, GE or NE solution 根据题目描述\(dp\)即可. 时间复杂度:\(O(nm)\) C.

【2019.09.15】2019icpc上海网络赛

题目地址:https://www.jisuanke.com/contest/3003?view=challenges A: B: C: D: E: F: G: H: I: J: K: L: 原文地址:https://www.cnblogs.com/ncu2019/p/11565345.html

2019年ICPC南昌网络赛 J. Distance on the tree 树链剖分+主席树

边权转点权,每次遍历到下一个点,把走个这条边的权值加入主席树中即可. #include<iostream> #include<algorithm> #include<stdio.h> #include<string.h> using namespace std; const int maxx = 2e5+10; struct node{ int l,r,cnt; }tree[maxx*40]; int head[maxx],rk[maxx],siz[maxx

【2019上海网络赛】

B题:分成两序列,A>B,A-(a[i]属于A里面的)<=B; #include<bits/stdc++.h>using namespace std;#define ll long longconst ll mod=1e9+7;const ll inf=1e18;ll dp[300*500+5];int a[300];int main(){ int t; scanf("%d",&t); while(t--) { int n,s=0; scanf(&quo