zoukankan      html  css  js  c++  java
  • Hash学习笔记

    @

    Hash简介:

    对于某些题目而言,在判断相等的时候,有时我们来不及或不会打一些算法了,甚至有时候连状压都会炸,这个时候,Hash无非是最好的选择。

    Hash能够将一些状态通过一些方式把他们压缩到一个区间里面的某些值,并用bool类型来判重就可以了,耶!

    题面懒得写了,传送门

    一道不错的模版。KMP乱入

    这道题是字符串Hash。

    我们定义两个互质的常量,(b)(h)。(注意(b<h))

    对于一个字符串(a),我们定义他的Hash函数值为(C(a,n)),代表(a)字符串从(1)(n)的字符子串的Hash值,函数的解析式是什么呢?

    我们就定义为:
    妈妈,我终于会写数学公式了

    [C(a,n)=(a[1]*b^{n-1}+a[2]*b^{n-2}+...+a[n]*b^{0})mod h ]

    现在,我们设A字符串长度为(n),那我们就是求(C(a,i(iin[1,n])))
    怎么求?
    暴力每次把1到i的子串算一遍
    肯定有更简便的做法。

    [C(a,i)=C(a,i-1)*b+A[i] ]

    但是问题怎么求?我们设(m)为B串的长度,(ed=C(B,m))

    那么,我们就要求(a)串中任意一个长度为(m)的子串的Hash值来做比较,首先,设这个子串的最左边的字符在(a)的位置为(l),而最右边的为(r),且(r-l+1=m),那么,我们现在已经知道了(C(a,r))(C(a,l-1))

    那么怎么搞?

    那么我们是不是要将(C(a,r))(C(a,l-1))([1,l-1])的部分抵消掉。

    [C(a,r)-C(a,l-1)? ]

    不不不。
    仔细观察,我们发现,在(C(a,r))(C(a,l-1))的值被放大了(b^{m})
    因为(r-(l-1)=r-l+1=m)

    那么,式子出来了。

    [C(a,r)-C(a,l-1)*b^{m}=C(a,r)-C(a,r-m)*b^{m}pmod h ]

    那么,这样只要枚举r,然后就可以计算出在A中(r-n+1)(r)的子串的Hash值了。

    不过得先算出(b^{m})。。。

    当Hash值相同时,即为相等。

    Hash为什么不会冲突?
    我怎么知道,好像因为生日悖论,总之冲突的几率挺小的,当然(h)尽量定大一点的一个素数,如1e8+7与1e8+9,而(b)的取值随便啦。

    不过,Hash冲突的几率还是让我们放不下心来(不过在比赛中一般可以认为不冲突),有时为了保险起见,许多都用双哈希或三哈希,即将(h)值换一换,一般双哈希取1e8+7与1e8+9,概率就已经很小的了。

    当然(h)如果取(2^{32})(2^{64}),即为unsigned int或unsigned long long自然溢出,省去mod的时间也可以。

    本代码只用单哈希。

    #include<cstdio>
    #include<cstring>
    using  namespace  std;
    typedef  long  long  ll;
    const  ll  h=100000007,b=130;//h的取值与b的取值
    ll  pp[1100000]/*求b的i0次方*/,tp[1100000]/*Hash值*/,n,m,ed,ans;
    char  st[1100000],stp[1100000];//A串与B串
    int  main()
    {
        scanf("%s%s",st+1,stp+1);n=strlen(st+1);m=strlen(stp+1);//长度
        pp[1]=b;
        for(int  i=2;i<=n;i++)pp[i]=(pp[i-1]*b)%h;//计算
        for(int  i=1;i<=n;i++)tp[i]=(tp[i-1]*b+st[i])%h;//求Hash值
        for(int  i=1;i<=m;i++)ed=(ed*b+stp[i])%h;//求B串的Hash值
        for(int  i=m;i<=n;i++)
        {
            ll  x;x=((tp[i]-tp[i-m]*pp[m])%h+h)%h;//求子串Hash值
            ans+=(x==ed);//统计答案
        }
        printf("%lld
    ",ans);
        return  0;
    }
    

    Hash表

    我承认我懒

    我们有时候需要表示一个集合{(1,2,11,22,1000)}
    并且以后还要时不时的加入新的数,并且删除原有的数,还有查找一些数字是否存在。

    离散化
    你家离散化支持强制在线?

    我们用一个长度为5的数组存,但是时间巨大,不得不让我们面壁思过考虑新的做法。
    我们用Hash?
    bool?你自己玩去吧,就算(O(1))时间,空间定这么大,亏呀!
    某大佬:Hash表是什么?直接Hash硬上就行了

    好吧,Hash确实可以做,但是要你算出这个数字的位置,你就得定int了。。。
    在这里插入图片描述

    这个时候,我们不得不借力新的数据结构,Hash表!

    What is this?

    如同Hash一样,我们定义一个函数: (C(x)=xmod k)(k)你自己定。
    我们让(k=11)
    那么我们定一个11的数组就好了,耶!

    这很像Hash,但不是Hash。
    如果是Hash就重复了,(11,22)

    你自己撞枪口上还硬说Hash是错的

    人家有自己的名字,Hash函数。不就是Hash

    Hash函数适用于Hash表中。

    继续。

    那么,我们可以把11与22一起丢到0里面去,什么意思?

    我们将每个位置定义一个单向链表,而(last[i])记录的是最后一个丢到这个位置的节点的编号。

    如图:
    在这里插入图片描述

    好丑。。。

    图就是这样,一个单向链表,不过左边的那一个(0,1,2,3,4,...,10)不用定义,直接用(last[i])代表第(i)个位置插入的最后一个数的编号是多少就行了。

    找的话先计算一遍要找的数的函数值,然后找到位置跳链表就行了,速度也挺快的。

    但是Hash表的速度取决与Hash函数的值分布的均不均匀。

    比较均匀的Hash函数从书上学的分几类:

    1. 除余法。
      就是模以一个数字(k)而已。但是如果运气不好定义的(k)是大部分数的公因数。。。那你就是非酋了。。。所以(k)最好定义一个大一点的质数,如:1000003、1000019、999979、100000007、100000009。

    2. 乘积取整法
      乘一个在((0,1))范围内的实数A,最好是无理数,(frac{sqrt{5}-1}{2})是一个实际效果很好。貌似这句话有点眼熟。。。 然后再取小数,乘以Hash表的大小向下取整。
      不过这样的话,若数字集中一块,估计很快就凉了,而且各种操作还会导致速度变慢。

    3. 基数转换法
      将原来的数字当成(k(k>10))进制对待,然后转成10进制,就是转换过程稍稍慢了点。

    4. 平方取中法
      将x取平方,然后根据Hash表实际大小能存多少位,来取中间几位,就是转换过程慢了点,但是分布就挺均匀的。

    当然,只要速度快,都是好的Hash函数,Hash函数总的来说还是得根据题目定,不过因为是随机数据,我一般用除余法,方便,还挺快!

    对于上面那道题,我们可以假设每个字符串的值就是他的Hash函数,(b=300,h=100000007),然后再用除余法走一遍,貌似没什么问题的样子。

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using  namespace  std;
    typedef  long  long  ll;
    const  ll  b=300,h=100000007,k=1000019;//常数
    struct  node
    {
        ll  l/*前一个插入到这个位置的数*/,y/*原本的数字*/;//单向链表
    }a[310000];ll  last[1100000]/*Hash表中的last数组*/,n,len/*整个hash表有多少节点*/;
    char  sp[30000];//char数组
    inline  ll  Hash()//求每个字符串的hash值
    {
        ll  ans=0,tlen=strlen(sp+1);
        for(ll  i=1;i<=tlen;i++)ans=(ans*b+sp[i])%h;
        return  ans;
    }
    inline  void  add(ll  x)//添加x
    {
    	ll  y=x;x%=k;//除余法
        for(ll  i=last[x];i;i=a[i].l)
        {
            if(a[i].y==y)return  ;//如果原本有的,就不加了。
        }
        a[++len].y=y;a[len].l=last[x];last[x]=len;//添加。
    }
    inline  bool  find(ll  x)//寻找
    {
    	ll  y=x;x%=k;//除余法
        for(ll  i=last[x];i;i=a[i].l)
        {
            if(a[i].y==y)return  true;//找到了,返回true
        }
        return  false;
    }
    char  st[2][10]={"no","yes"};
    int  main()
    {
        scanf("%lld",&n);
        for(ll  i=1;i<=n;i++)
        {
            scanf("%s",sp+1);
            if(sp[1]=='a')
            {
                gets(sp);//注意,用get的话会把add与字符串中间的空格读进来,所以不用+1
                add(Hash());//添加
            }
            else
            {
                gets(sp);//同理
                printf("%s
    ",st[ find(Hash()) ]);//寻找然后输出
            }
        }
        return  0;
    }
    

    更多的题目来了。。。


    First

    暴力枚举子字符串长度,时间(O(nlogn))我也不知道为什么

    #include<cstdio>
    #include<cstring>
    #include<cstdlib>
    using  namespace  std;
    typedef  long  long  ll;
    const  ll  b=300,h=100000007;
    ll  hash[2100000],n,po[2100000];
    char  st[2100000];
    inline  ll  inhash(ll  x,ll  y){return  ((hash[y]-hash[x]*po[y-x])%h+h)%h;}//计算x+1到y的hash值
    int  main()
    {
        po[1]=b;
        for(ll  i=2;i<=1000000;i++)po[i]=(po[i-1]*b)%h;//计算阶乘
        while(1)
        {
            scanf("%s",st+1);
            n=strlen(st+1);
            if(n==1  &&  st[1]=='.')break;
            for(ll  i=1;i<=n;i++)hash[i]=(hash[i-1]*b+st[i])%h;//计算hash值
            for(ll  i=1;i<=n;i++)
            {
                if(n%i==0)//一个优化
                {
                    bool  bk=true;
                    for(ll  j=i;j<=n;j+=i)
                    {
                        if(inhash(j-i,j)!=hash[i])//判断
                        {
                            bk=false;
                            break;
                        }
                    }
                    if(bk==true)//可以,输出
                    {
                        printf("%lld
    ",n/i);
                        break;
                    }
                }
            }
        }
        return  0;
    }
    

    Second

    暴力枚举前缀长度,每次(O(n))。。。

    #include<cstdio>
    #include<cstring>
    using  namespace  std;
    typedef  long  long  ll;
    const  ll  b=300,h=100000009;
    ll  hash[510000],po[510000],n,ans[510000];
    char  st[510000];
    inline  ll  inhash(ll  x,ll  y){return  ((hash[y]-hash[x]*po[y-x])%h+h)%h;}//求[x+1,y]的hash值。
    int  main()
    {
        po[1]=b;
        for(ll  i=2;i<=400000;i++)po[i]=(po[i-1]*b)%h;//预处理阶乘
        while(scanf("%s",st+1)!=EOF)
        {
            ans[0]=0;
            n=strlen(st+1);
            for(ll  i=1;i<=n;i++)hash[i]=(hash[i-1]*b+st[i])%h;//hash值
            for(ll  i=1;i<=n;i++)
            {
                if(inhash(0,i)==inhash(n-i,n))ans[++ans[0]]=i;//记录
            }
            for(ll  i=1;i<ans[0];i++)printf("%lld ",ans[i]);//输出
            printf("%lld
    ",ans[ans[0]]);
        }
        return  0;
    }
    

    Third

    没错,枚举那个位置是插进去的,然后推一下如何计算剔除这个位置的hash值,然后就没毛病了。

    #include<cstdio>
    #include<cstring>
    #include<cstdlib>
    using  namespace  std;
    typedef  long  long  ll;
    const  ll  b=300,h=100000009;
    ll  po[2100000],hash[2100000],n,m,kk/*kk就是个优化*/,ans,cnt=0,sit=0;/*统计答案三人组*/
    char  st[2100000];
    inline  ll  inhash(ll  x,ll  y){return  ((hash[y]-hash[x]*po[y-x])%h+h)%h;}//求出[x+1,y]的hash值。
    inline  ll  hash_hash(ll  x,ll  y,ll  k)//在[x,y]的区间里把k剔除
    {
        ll  xk=inhash(x-1,k-1),ky=inhash(k,y);//计算出两段hash值
        return  (xk*po[y-k]+ky)%h;//拼在一起
    }
    int  main()
    {
        scanf("%lld",&n);
        scanf("%s",st+1);
        po[0]=1;/*因为在inhash中会出现po[0]的情况*/po[1]=b;
        for(ll  i=2;i<=n;i++)po[i]=(po[i-1]*b)%h;
        for(ll  i=1;i<=n;i++)hash[i]=(hash[i-1]*b+st[i])%h;
        m=n/2+1;
        kk=inhash(m,n);
        for(ll  i=1;i<=m;i++)//模拟字符在前一个字符串里
        {
            if(hash_hash(1,m,i)==kk)
    		{
                if(ans==1)//判断
                {
                    if(kk!=sit)//不是同一个字符串
                    {
                        printf("NOT UNIQUE
    ");//不止一组解
                        return  0;
                    }
                }
    			else  ans++,cnt=i,sit=kk;//统计答案
    		}
            if(ans==2)
            {
                printf("NOT UNIQUE
    ");//不止一组解
                return  0;
            }
        }
        kk=inhash(0,m-1);//减少hash的步骤
        for(ll  i=m;i<=n;i++)//模拟字符在后一个字符串里
        {
            if(hash_hash(m,n,i)==kk)
            {
                if(ans==1)//判断
                {
                    if(kk!=sit)//不是同一个字符串
                    {
                        printf("NOT UNIQUE
    ");//不止一组解
                        return  0;
                    }
                }
    			else  ans++,cnt=i,sit=kk;//统计答案
            }
        }
        if(ans==0)printf("NOT POSSIBLE
    ");//输出
        else//输出字符串
        {
            if(cnt<=m)
            {
                for(ll  i=1;i<=m;i++)
                {
                    if(i!=cnt)printf("%c",st[i]);
                }
            }
            else
            {
                for(ll  i=m;i<=n;i++)
                {
                    if(i!=cnt)printf("%c",st[i]);
                }
            }
            printf("
    ");
        }
        return  0;
    }
    

    其实我也发现我代码有很多多余的判断,反正都A了,不管他


    可恶至极的一道题!

    像上面一样?(O(qnlogn)),显然,毒瘤出题人不会让我们过。。。

    我们考虑优化。

    以前我们判断这个是否是循环节,我们都是for一遍,但是,有更好的方法。

    假设当前区间为([l,r]),循环节长度为(k(k|(r-l+1))),那么,当([l+k,r])的Hash值等于([l,r-k])的Hash值时,就代表这个是循环节!

    为什么?

    假设长度为(k)的循环节在长度为(n)的字符串中标号为(a_{1},a_{2},a_{3},...,a_{n/k}(a_{1},a_{2},a_{3}...)的长度都为(k))
    那么,Hash值相等代表(a_{1}=a_{2}...),所以可以呀。

    其实想想都觉的可以。。。

    但是时间复杂度还是(O(qn)),如果可以过,就不会有出题人收到刀片的事情了。。。

    由于我们判断了(k)是否是(n)的约束了,所以时间为(O(qsqrt{n}))

    但是还是不能过!

    考虑循环节的性质,如果(k)是其中一个循环节,那么(ki(ki|n))(ki代表(k*i),i为任意数字)也是一个循环节,所以我们先线性筛一遍,记录出每个数的最小约数,然后初始化(now=n)代表当前的循环节最小为(now),不断找(n)的约数(k_{i})判断(now)能否继续分成(k_{i})块,可以的话(now/k_{i}),代表当前的循环节还可以分下去,知道找完所有约数为止。

    那么如何找到所有的约数?
    由于我们记录了每个数的最小约数(记录这个会比较好写),那么初始化(v=n),然后不断取(v)的最小约数,然后再除于这个最小的约数,不断下去,就行了。

    丑陋的卡常代码,为了卡常,我用了溢出的方法来进行模。

    #include<cstdio>
    #include<cstring>
    #define  RI  register  int//寄存器
    using  namespace  std;
    typedef  unsigned  long  long  ll;
    const  ll  b=53;//一个非偶数质数
    ll  ans,n,m;
    ll  hash[510000],po[510000];//日常数组
    char  ss[510000];
    inline  void  in(ll  &x)//快读
    {
        x=0;char  c=getchar();
        while(c<'0'  ||  c>'9')c=getchar();
        while('0'<=c  &&  c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
    }
    inline  void   out(ll  x)//快输
    {
        register  ll  y=10;
        while(y<=x)y=(y<<3)+(y<<1);
        while(y!=1)y/=10,putchar(x/y+48),x%=y;
        putchar('
    ');
    }
    inline  ll  inhash(ll  x,ll  y){return  hash[y]-hash[x]*po[y-x];}
    inline  bool  check(ll  x,ll  y,ll  t)//在x到y区间,循环节长度为t
    {
        register  ll  xt=inhash(x-1,y-t),yt=inhash(x+t-1,y);//卡常大法好
        if(xt==yt)return  true;
        return  false;
    }
    bool  f[510000];
    int  pri[510000],next[510000];
    inline  void  shuai()//欧拉筛
    {
        for(int  i=2;i<=n;i++)
        {
            if(f[i]==false)
            {
                pri[++pri[0]]=i;
                next[i]=i;//素数就时他自己
            }
            for(int  j=1;j<=pri[0]  &&  i*pri[j]<=n;j++)
            {
                f[i*pri[j]]=true;
                next[i*pri[j]]=pri[j];//自己想想为什么是最小约数
                if(i%pri[j]==0)break;
            }
        }
    }
    int  main()
    {
        in(n);
        scanf("%s",ss+1);
        po[0]=1;po[1]=b;
        for(RI  i=2;i<=n;i++)po[i]=po[i-1]*b;
        for(RI  i=1;i<=n;i++)hash[i]=hash[i-1]*b+ss[i];
        shuai();//初始化
        in(m);
        for(RI  i=1;i<=m;i++)
        {
            ll  l,r;in(l),in(r);
            ll  v=ans=r-l+1;//如文中所说,ans就是now
            while(v!=1)
            {
                if(check(l,r,ans/next[v]))ans/=next[v];
                v/=next[v];
            }
            printf("%llu
    ",ans);//输出
        }
        return  0;
    }
    

    让我想报复社会报答出题人的一道题。。。

    首先,记住两个数字,2333与98754321,膜了题解才AC,变态出题人!

    把数组当成字符串,暴力枚举呀,用map判重突然发现这玩意挺牛逼的!(O(nlog^{2}{n})),就是前一遍Hash,后一遍Hash。

    其实虽然(b,h)参数并没变,但是两次Hash的方法不同也可以起到很厉害的作用。

    优化:如果当前枚举的块的长度所分的块还没有(anss)(目前能获得的最大不同的段的个数)大,那么退役吧。。。 break

    #include<cstdio>
    #include<cstring>
    #include<map>//map大法好呀!
    #define N 200010//数组大小
    using  namespace  std;
    typedef  long  long  ll;
    const  ll  b=2333,h=98754321;//变态出题人
    map<ll,bool> ss;//map
    ll  n;
    ll  hash1[N]/*正着*/,hash2[N]/*倒着*/,po[N],ans[N],anss/*记录能分成多少段*/,a[N];
    inline  ll  inhash1(ll  x,ll  y){return  ((hash1[y]-hash1[x]*po[y-x])%h+h)%h;}//正着[x+1,y]
    inline  ll  inhash2(ll  x,ll  y){return  ((hash2[x]-hash2[y]*po[y-x])%h+h)%h;}//倒着[x,y-1]
    int  main()
    {
        scanf("%lld",&n);
        po[0]=1;
        for(ll  i=1;i<=n;i++)
        {
            scanf("%lld",&a[i]);
            po[i]=(po[i-1]*b)%h;//次方
            hash1[i]=(hash1[i-1]*b+a[i])%h;//正着
        }
        for(ll  i=n;i>=1;i--)hash2[i]=(hash2[i+1]*b+a[i])%h;//倒着
        for(ll  i=1;i<=n;i++)
        {
            if(n/i<anss)break;//优化
            ss.clear();//清空
            ll  now=0;//目前所能获得的最大块数
            for(ll  j=i;j<=n;j+=i)
            {
                ll  zheng,dao;
                zheng=inhash1(j-i,j);dao=inhash2(j-i+1,j+1);
                if(!ss.count(zheng)  ||  !ss.count(dao))//不用质疑,打&&会WA,可能是因为用Hash其中有一个会重复,这也体现了正着来一遍,反着来一遍可以起到判重作用
                {
                    ss[zheng]=true;ss[dao]=true;//扔到map里面去
                    now++;
                }
            }
            if(now==anss)ans[++ans[0]]=i;//新增答案
            else  if(now>anss)//更新新的答案
            {
                anss=now;
                ans[ans[0]=1]=i;
            }
        }
        printf("%lld %lld
    ",anss,ans[0]);
        for(ll  i=1;i<ans[0];i++)printf("%lld ",ans[i]);
        printf("%lld
    ",ans[ans[0]]);//输出
        return  0;
    }
    

    终于有二分的题了

    二分加Hash

    首先串必须是偶数长度(不然你中间的字符去反后就不等于自己了)

    而且,若([l,r])是反对称,那么([l-1,r-1])也是反对称。

    正着一遍Hash,弄成反串再反着来一遍Hash,那么,假设我们有两个数字(l,k),那么,我们就是求([l,l+2*k-1])

    那么,先用循环(i)枚举(l+k-1),然后,然后里面用二分枚举(l),然后用(i)(l)计算出(r),设二分结果为(x),然后(ans+=(i-x)+1)

    代码:

    #include<cstdio>
    #include<cstring>
    #define  N  1100000
    using  namespace  std;
    typedef  long  long  ll;
    const  ll  b=2333,h=100000007;
    ll  hash1[N]/*正着*/,hash2[N]/*反串反着哈希*/,po[N]/*次方*/,n,ans;
    char  st[N];
    inline  ll  inhash1(ll  x,ll  y){return  ((hash1[y]-hash1[x]*po[y-x])%h+h)%h;}//[x+1,y]
    inline  ll  inhash2(ll  x,ll  y){return  ((hash2[x]-hash2[y]*po[y-x])%h+h)%h;}//[x,y-1]
    inline  ll  mymax(ll  x,ll  y){return  x>y?x:y;}//取最大 
    inline  bool  check(ll  l,ll  r)
    {
        ll  zh=inhash1(l-1,r)/*正着*/,da=inhash2(l,r+1)/*倒着*/;
        return  zh==da;
    }
    int  main()
    {
        scanf("%lld",&n);
        scanf("%s",st+1);
        po[0]=1;
        for(int  i=1;i<=n;i++)
        {
            po[i]=(po[i-1]*b)%h;
            hash1[i]=(hash1[i-1]*b+st[i])%h;
        }
        for(int  i=n;i>=1;i--)hash2[i]=(hash2[i+1]*b+((st[i]-48)^1)+48)%h;//计算hash 
        for(int  i=1;i<=n;i++)
        {
            ll  l=mymax(1,i-(n-i)+1)/*计算l区间*/,r=i,mid,x=i+1/*记录答案*/;//二分 
            while(l<=r)
            {
                mid=(l+r)/2;
                if(check(mid,i+(i-mid+1)))r=mid-1,x=mid;//成立,缩小限制 
                else  l=mid+1;//不成立,放大限制 
            }
            ans+=(i-x)+1;//统计 
        }
        printf("%lld
    ",ans);//输出 
        return  0;
    }
    

    第一道Hash表的练习题

    终于来了道Hash表的题,但是好水。QAQ

    #include<cstdio>
    #include<cstring>
    using  namespace  std;
    typedef  long  long  ll;
    const  int  k=2999999;//一个好看的质数。。。
    struct  node
    {
        int  l;
        ll  x;
    }hash[2100000];int  len,last[k];
    inline  void  add(ll  x)//x肯定没出现过
    {
        ll  y=x;x%=k;//转换
        hash[++len].x=y;hash[len].l=last[x];last[x]=len;
    }
    inline  bool  find(ll  x)//找到x
    {
        ll  y=x;x%=k;
        for(int  i=last[x];i;i=hash[i].l)//找Hash表
        {
            if(hash[i].x==y)return  1;
        }
        return  0;
    }
    int  main()
    {
        ll  A,B,C,x=1,id=0;scanf("%lld%lld%lld",&A,&B,&C);A%=C;//不加也可以QAQ
        while(!find(x))
        {
            if(id==2000000){id=-1;break;}//-1判断
            add(x);//添加
            x=(A*x+(x%B))%C;id++;//更新
        }
        printf("%lld
    ",id);
        return  0;
    }
    

    一遍AC的快感

    这道题的话,一个支持删除的Hash表,也不是很难码。

    先说做法,维护(l,r)两个下标指针,每次用循环让(r++),然后找一下有没有与第(r)个位置重复的,有,就让(l)等于他,同时删掉之前的数字,然后用(ans)记录最大的区间。

    #include<cstdio>
    #include<cstring>
    #include<cstdlib>
    using  namespace  std;
    typedef  long  long  ll;
    const  ll  k=2999999;
    struct  node
    {
        ll  l,x,id;
    }hash[1100000];ll  last[k],len;
    ll  a[1100000],n,ans;
    inline  void  add(ll  x,ll  id)//添加
    {
        ll  y=x;x%=k;
        hash[++len].id=id;hash[len].x=y;hash[len].l=last[x];last[x]=len;
    }
    inline  ll  find(ll  x)//返回重复的ID
    {
        ll  y=x;x%=k;
        for(ll  i=last[x];i;i=hash[i].l)
        {
            if(hash[i].x==y)return  hash[i].id;
        }
        return  0;//没有返回0
    }
    inline  void  del(ll  x,ll  id)//删除编号为id的
    {
        x%=k;
        if(last[x]==id)last[x]=hash[id].l;//如果是最后一个插入的
        else//否则像单向链表一样删除
        {
            ll  pre=last[x];//之前的节点
            for(int  i=hash[pre].l;i;i=hash[i].l)//枚举那个节点被删除
            {
                if(hash[i].id==id)//判断
                {
                    hash[pre].l=hash[i].l;
                    return  ;
                }
                pre=i;//更新
            }
        }
    }
    inline  ll  mymax(ll  x,ll  y){return  x>y?x:y;}//取最大
    int  main()
    {
        scanf("%lld",&n);
        for(ll  i=1;i<=n;i++)scanf("%lld",&a[i]);//输入
        ll  l=1;
        for(ll  i=1;i<=n;i++)
        {
            ll  tkk=find(a[i]);//找到重复的位置
            for(;l<=tkk;l++)del(a[l],l);//删除
            add(a[i],i);//添加
            ans=mymax(ans,i-l+1);//记录
        }
        printf("%lld
    ",ans);
        return  0;
    }
    

    终于在今天做完哈希和哈希表这一章节了。

    注意事项

    在设置指数时不能有两个位置重复,例如在二维hash中,如果把((i,j))的指数设置成了(i+j),那么((1,2))((2,1))的位置可以互通,就不能保证数字的唯一性了。

    同时参数的设置最好比数域要大,字符串的Hash,(b)就要比'z'要大,不然举个例子,数域是在(1-9)的,我设置一个(3),那是不是以为着(1)号位的(9)可以看作(2)号位的(3),模数就不说了,你设置这么小能起到判重的作用吗?

  • 相关阅读:
    Linux环境定时备份mysql数据库
    idea以DEBUG方式启动项目卡住,但是不报错
    Linux查看防火墙,开放端口
    element动态添加删除表格的行数
    触发器编写,执行插入或update分别执行不同sql
    vue数组判断数值,遍历,过滤
    将某文件夹下的文件压缩成zip
    转载:win10专业版取消自动更新
    IIS启动应用程序池报错"服务无法在此时接受控制信息
    IIS7 设置网站默认主页(首页)
  • 原文地址:https://www.cnblogs.com/zhangjianjunab/p/9932200.html
Copyright © 2011-2022 走看看