字符串哈希板子 & 瞎做

参考资料

  1. HASH算法模板以及简单的入门题总结
  2. 【算法学习】字符串Hash入门

字符串Hash在某些情况下要比map好用,因为在对字符串进行预处理后可以O(1)时间查询任意子串的哈希值。

貌似现在出题人都会卡自然溢出(与BASE的选取无关),用双Hash或自己搞两个模数可能会保险些,,,

模板

//-----自然溢出-----//

const ULL BASE = 13331;
ULL p[MAXN];
void init()
{
    p[0]=1;
    for(int i=1;i<MAXN;i++)
        p[i]=p[i-1]*BASE;
}

struct myHash
{
    ULL h[MAXN];
    hash(const char str[]) {init(str);}
    void init(const char str[])
    {
        int n=strlen(str);
        h[n]=0;
        for(int i=n-1;i>=0;i--) //倒着hash i位为最低位 n为最高位 方便获取子串hash值
            h[i]=h[i+1]*BASE+str[i]-'A'+1;
    }
    ULL get_hash(int i,int length) //从下标i开始长度为length的字串的hash值
    {
        return h[i]-h[i+length]*p[length];
    }
};


//--------hash二维矩阵---------//

//二维hash 取两个base
//统计b矩阵在a矩阵中的出现次数

const ULL base1 = 13331;
const ULL base2 = 23333;
ULL p1[MAXN],p2[MAXN];
void pre()
{
    p1[0]=p2[0]=1;
    for(int i=1;i<MAXN;i++)
        p1[i]=p1[i-1]*base1,
        p2[i]=p2[i-1]*base2;
}
int T,n1,m1,n2,m2;
char a[MAXN][MAXN],b[MAXN][MAXN];
ULL h[MAXN][MAXN];
void init()
{
    for(int i=1;i<=n1;i++)
        for(int j=1;j<=m1;j++)
            h[i][j]=h[i][j-1]*base2+h[i-1][j]*base1
                -h[i-1][j-1]*base1*base2+(a[i][j]-'A'+1);
}
ULL get_hash(int x,int y,int lx,int ly)
{
    return  +h[x+lx-1][y+ly-1]
            -h[x+lx-1][y-1]*p2[ly]
            -h[x-1][y+ly-1]*p1[lx]
            +h[x-1][y-1]*p1[lx]*p2[ly];
}

int work()
{
    pre();
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n1,&m1);
        for(int i=1;i<=n1;i++) scanf("%s",a[i]+1);
        scanf("%d%d",&n2,&m2);
        for(int i=1;i<=n2;i++) scanf("%s",b[i]+1);

        init();

        ULL t=0;
        for(int i=1;i<=n2;i++)
            for(int j=1;j<=m2;j++)
                t+=(b[i][j]-'A'+1)*p1[n2-i]*p2[m2-j];//注意下标

        int ans=0;
        for(int i=1;i<=n1-n2+1;i++)
            for(int j=1;j<=m1-m2+1;j++)
                if(get_hash(i,j,n2,m2)==t)
                    ans++;
        printf("%d\n",ans);
    }
    return 0;
}

//-----双hash-----//

const ULL MOD1 = 402653189;
const ULL MOD2 = 805306457;

const ULL BASE1 = 13331;
const ULL BASE2 = 23333;

//备用 模数&base
//const ULL MOD1 = 1610612741;
//const ULL MOD2 = 1222827239;

//const ULL BASE1 = 1331;
//const ULL BASE2 = 131;

ULL p[MAXN][2];
void init()
{
    p[0][0]=p[0][1]=1;
    for(int i=1;i<MAXN;i++) p[i][0]=p[i-1][0]*BASE1%MOD1;
    for(int i=1;i<MAXN;i++) p[i][1]=p[i-1][1]*BASE2%MOD2;
}
struct myHash
{
    ULL h[MAXN][2];
    void init(char s[],int n=0)
    {
        n=strlen(s),h[n][0]=h[n][1]=0;
        for(int i=n-1;i>=0;i--) h[i][0]=(h[i+1][0]*BASE1%MOD1+s[i]-'A'+1)%MOD1;
        for(int i=n-1;i>=0;i--) h[i][1]=(h[i+1][1]*BASE2%MOD2+s[i]-'A'+1)%MOD2;
    }
    upr get_hash(int pos,int length)
    {
        return {
            ((h[pos][0]-h[pos+length][0]*p[length][0]%MOD1)+MOD1)%MOD1,
            ((h[pos][1]-h[pos+length][1]*p[length][1]%MOD2)+MOD2)%MOD2
        };
    }
};
upr make_hash(char s[])
{
    int n=strlen(s);
    upr ans;
    for(int i=n-1;i>=0;i--)
    {
        ans.fi=(ans.fi*BASE1%MOD1+s[i]-'A'+1)%MOD1;
        ans.se=(ans.se*BASE2%MOD2+s[i]-'A'+1)%MOD2;
    }
    return ans;
}

参考资料一里面的题就不放这里了,,,贴几个学长博客没有的题(camp上讲字符串hash的例题)

1.BZOJ3555 [Ctsc2014]企鹅QQ

题意:当两个字符串长度相等且仅有一个位置上的字符不同时我们说这两个字符串相似,给定N个长度为L且互不相同的字符串,求有多少对字符串是相似的。

题解:先对每一个字符串预处理出前缀哈希值和后缀哈希值,这样就可以在O(1)的时间内查询字符串去掉某一个字符后的哈希值,枚举字符串去掉的是哪一位,将所有字符串去掉这一位后的哈希值丢到一起排下序,O(n)统计答案即可,总时间复杂度 O ( L ( N l o g N + 2 N ) ) O(L(NlogN+2N)) O(L(NlogN+2N))

const ULL BASE = 13331;
ULL p[N];
vector<char> charset;
void init()
{
    for(char c='0';c<='9';c++) charset.pb(c);
    for(char c='A';c<='z';c++) charset.pb(c);
    charset.pb('@'),charset.pb('_'),sort(all(charset)),unq(charset);
    p[0]=1;
    for(int i=1;i<N;i++)
        p[i]=p[i-1]*BASE;
}
inline int index(char c){return lower_bound(all(charset),c)-charset.begin()+1;}
struct myHash
{
    ULL pre[N];
    ULL h[N];
    void init(char s[])
    {
        int n=strlen(s+1);
        pre[0]=0;
        for(int i=1;i<=n;i++)
            pre[i]=pre[i-1]+index(s[i])*p[i-1];
        h[n+1]=0;
        for(int i=n;i>=1;i--)
            h[i]=h[i+1]*BASE+index(s[i]);
    }
    ULL sub_hash(int i){return pre[i-1]+h[i+1]*p[i-1];}
};
 
int n,l,S;
char s[MAXN];
myHash h[MAXN];
vector<ULL> a[N];
LL ans=0;
 
int work()
{
    init();
    scanf("%d%d%d",&n,&l,&S);
    for(int i=1;i<=n;i++)
        scanf("%s",s+1),h[i].init(s);
    for(int i=1;i<=l;i++)
        for(int j=1;j<=n;j++)
            a[i].pb(h[j].sub_hash(i));
    for(int i=1;i<=l;i++)
    {
        sort(all(a[i]));
        int cnt=1;;
        for(int j=1;j<=a[i].size();j++)
        {
            if(j==a[i].size()||a[i][j]!=a[i][j-1])
                ans+=(cnt>=2)?C(cnt,2):0,cnt=1;
            else cnt++;
        }
    }
    return printf("%lld\n",ans);
}

2.BZOJ 2084[Poi2010]Antisymmetry

题意:对于一个只包含01的字符串,如果把它按位取反后翻转的结果和原字符串是一样的,我们称这个字符串是反对称的,求给定01串中反对称子串的个数。

题解:Manacher模板题 首先反对称串的长度一定偶数,而且如果子串 [ L , R ] [L,R] [L,R]是反对称串的话, [ L − 1 , R − 1 ] [L-1,R-1] [L1,R1]也一定是反对称串,所以我们可以枚举对称点并二分答案计算以这个点为对称点的反对称串的长度,记得预处理出原串取反并翻转后的哈希值,这样就可以在二分答案时 O ( 1 ) O(1) O(1)判断了,总时间复杂度 O ( N l o g N ) O(NlogN) O(NlogN)

const ULL BASE = 13331;
ULL p[N];
void init()
{
    p[0]=1;
    for(int i=1;i<N;i++)
        p[i]=p[i-1]*BASE;
}
inline int index(char c){return c-'0';}
struct myHash
{
    ULL pre[N];
    ULL h[N];
    void init(char s[],int n=0)
    {
        n=strlen(s+1),h[n+1]=0;
        for(int i=n;i>=0;i--)
            h[i]=h[i+1]*BASE+index(s[i]);
    }
    ULL get_hash(int i,int l){return h[i]-h[i+l]*p[l];}
};
 
int n;
LL cnt=0;
char s1[MAXN],s2[MAXN];
myHash hash1,hash2;
 
bool check(int i,int l)
{
    int j=n-i-1;
    return hash1.get_hash(i-l+1,l*2)==hash2.get_hash(j-l,l*2);
}
 
int work()
{
    init();
    scanf("%d",&n);
    scanf("%s",s1);
    for(int i=0;i<n;i++)
        s2[i]=(s1[n-i-1]=='0'?'1':'0');
 
    hash1.init(s1),hash2.init(s2);
    for(int i=0;i<n-1;i++)
    {
        int l=0,r=min(i+1,n-i-1),ans=0;
        while(l<=r)
        {
            if(check(i,mid))
                ans=mid,l=mid+1;
            else r=mid-1;
        }
        cnt+=ans;
    }
    return printf("%lld\n",cnt);
}

3.BZOJ1014 [JSOI2008]火星人prefix

题意:询问字符串两个后缀的最长公共前缀,要求支持插入,修改操作。

题解:Splay维护序列的Hash值,二分答案求最长公共前缀。
个Splay调一下午
感觉我写的Splay自带大常数,,,10S时限9.988S跑过,,,

const ULL BASE = 1331;
ULL p[N];
void init_hash()
{
    p[0]=1;
    for(int i=1;i<N;i++)
        p[i]=p[i-1]*BASE;
}
 
namespace Splay
{
    char val[MAXN];
    int siz[MAXN],fa[MAXN],ch[MAXN][2];
    int root,node_cnt;
    ULL h[MAXN];
 
    void newnode(int &x,int f,char c)
    {
        x=++node_cnt;
        siz[x]=1,fa[x]=f,val[x]=c,h[x]=c-'a'+1;
    }
    void pushup(int x)
    {
        if(!x) return;
        siz[x]=siz[ls]+siz[rs]+1;
        h[x]=h[ls]+(val[x]-'a'+1)*p[siz[ls]]+h[rs]*p[siz[ls]+1];
    }
    void build(int &x,int l,int r,int f,char a[])
    {
        if(l>r) return;
        newnode(x,f,a[mid]);
        build(ls,l,mid-1,x,a);
        build(rs,mid+1,r,x,a);
        pushup(x);
    }
    void init(char a[],int n)
    {
        newnode(root,0,'a'-1);
        newnode(ch[root][1],root,'a'-1);
        build(key,1,n,ch[root][1],a);
        pushup(ch[root][1]),pushup(root);
    }
    inline bool chk(int x){return ch[fa[x]][1]==x;}
    void rotate(int x)
    {
        int y=fa[x],z=fa[y],k=chk(x),w=ch[x][k^1];
        if(w) fa[w]=y;ch[y][k]=w;
        fa[x]=z; if(z) ch[z][chk(y)]=x;
        ch[x][k^1]=y,fa[y]=x;
        pushup(y),pushup(x); if(z) pushup(z);
    }
    void splay(int x,int goal=0)
    {
        if(!x) return;
        while(fa[x]!=goal)
        {
            int y=fa[x],z=fa[y];
            if(z!=goal)
                (chk(x)==chk(y))?rotate(y):rotate(x);
            rotate(x);
        }
        if(!goal) root=x;
    }
    int find(int k,int x=root)
    {
        while(siz[ls]+1!=k)
        {
            if(siz[ls]>=k) x=ls;
            else k-=siz[ls]+1,x=rs;
        }
        return x;
    }
    void insert(int pos,char c)
    {
        pos++,splay(find(pos)),splay(find(pos+1),root);
        newnode(key,ch[root][1],c),pushup(ch[root][1]),pushup(root);
    }
    ULL get_hash(int l,int r)
    {
        l++,r++,splay(find(l-1)),splay(find(r+1),root);
        return h[key];
    }
    void change(int pos,char c)
    {
        pos++,splay(find(pos-1)),splay(find(pos+1),root);
        val[key]=c,h[key]=c-'a'+1;
        pushup(ch[root][1]),pushup(root);
    }
    void print(int x)
    {
        if(ls) print(ls);
        cout << dbgs4(x,siz[x],fa[x],val[x]) << " " << dbgs(h[x]) << endl;
        if(rs) print(rs);
    }
    void debug(){print(root);}
}
 
int n,m;
char s[MAXN];
 
int work()
{
    init_hash();
 
    scanf("%s",s+1);
    n=strlen(s+1);
    Splay::init(s,n);
 
    scanf("%d",&m);
    while(m--)
    {
        int x,y;
        char s[10];
        scanf("%s",s);
        if(s[0]=='I')
            scanf("%d",&x),scanf("%s",s),Splay::insert(x,s[0]),n++;
        else if(s[0]=='R')
            scanf("%d",&x),scanf("%s",s),Splay::change(x,s[0]);
        else
        {
            scanf("%d%d",&x,&y);
            int l=0,r=min(n-x+1,n-y+1),ans=0;
            while(l<=r)
            {
                if(Splay::get_hash(x,x+mid-1)==Splay::get_hash(y,y+mid-1))
                    ans=mid,l=mid+1;
                else r=mid-1;
            }
            printf("%d\n",ans);
        }
    }
    return 0;
}

你可能感兴趣的:(字符串哈希板子 & 瞎做)