《算法竞赛进阶指南》0x14Hash

Hash表

Hash表又称散列表,一般由Hash函数(散列函数)与链表共同实现。
有一种“开散列”的解决方案是,建立一个邻接表结构,以Hash函数的值域作为表头数组head,映射后的值相同的原始信息被分到同一类,构成一个链表接在对应的表头之后,链表上的节点信息可以保存原始信息和一些统计数据。
Hash表主要包括两个基本操作:
1.计算Hash函数的值;
2.定位到对应的链表中依次遍历,比较。

例题
acwing137.雪花雪花雪

定义Hash函数
H ( a i , 1 , a i , 2 , . . . , a i , 3 ) = ( ∑ j = 1 6 a i , j + ∏ j = 1 6 a i , j ) m o d P H(a_{i,1},a_{i,2},...,a_{i,3})=(\sum_{j=1}^{6}a_{i,j}+\prod_{j=1}^{6}a_{i,j})mod P H(ai,1,ai,2,...,ai,3)=(j=16ai,j+j=16ai,j)modP
显然,对于形状相同的雪花他们的Hash值相等。
建立哈希表,把N片雪花依次插入,对于每片雪花扫描其哈希值所对应的表头后面的链表,检查每一片没否和他相等。

#include<iostream>
#include<cstring>
using namespace std;
#define MAX_N 100000
int snow[MAX_N+5][6];
int head[MAX_N+5];
int nxt[MAX_N+5];
int p=9991;
int n;
int tot=0;
int H(int *a)
{
    int sum=0,mul=1;
    for(int i=0;i<6;i++)
    {
        sum=(sum+a[i])%p;
        mul=1ll*mul*a[i]%p;
    }
    return (sum+mul)%p;
}
bool equal(int *a,int *b)
{
    int flag;
    for(int i=0;i<6;i++)
    {
        for(int j=3;j<6;j++)
        {
            flag=1;
            for(int k=0;k<6;k++)
            {
                if(a[(i+k)%6]!=b[(j+k)%6])flag=0;
            }
            if(flag)return 1;
            flag=1;
            for(int k=0;k<6;k++)
            {
                if(a[(i+k)%6]!=b[(j-k+6)%6])flag=0;
            }
            if(flag)return 1;
        }
    }
    return 0;
}
bool insert(int *a)
{
    int val=H(a);
    for(int i=head[val];i;i=nxt[i])
    {
        if(equal(snow[i],a))return 1;
    }
    memcpy(snow[++tot],a,6*sizeof(int));
    nxt[tot]=head[val];
    head[val]=tot;
    return 0;
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        int a[10];
        for(int j=0;j<6;j++)
        scanf("%d",&a[j]);
        if(insert(a))
        {
            puts("Twin snowflakes found.");
            return 0;
        }
    }
    puts("No two snowflakes are alike.");
    return 0;
}

字符串Hash

取一固定值P,把字符串看作P进制数,并分配一个大于0的数值,代表每种字符。 一般来讲a=1,b=2,z=26。取一固定值M,求出该P进制数对M的余数,作为该字符串的Hash值。

一般来讲P取131或者13331,Hash值产生的冲突极低,只要Hash值相同,我们就可以认为字符串是相等的。通常我们取 M = 2 64 M=2^{64} M=264,即直接使用unsigned long long 类型存储这个Hash值,在计算式产生移除相当于直接对M取模。

如果已知字符串T的Hash值为H(S),字符串S+T的Hash值为H(S+T),那么字符串T的Hash值
H ( T ) = ( H ( S + T ) − H ( S ) ∗ P l e n g t h ( T ) H(T)=(H(S+T)-H(S)*P^{length(T)} H(T)=(H(S+T)H(S)Plength(T)

例题
acwing138.兔子与兔子

模板题

#include<iostream>
#include<cstring>
using namespace std;
typedef unsigned long long ull;
#define MAX_N 1000000
char s[MAX_N+5];
ull pwe[MAX_N+5]={1},sum[MAX_N+5];
int p=131;
int main()
{
    scanf("%s",s+1);
    int len=strlen(s+1);
    for(int i=1;i<=len;i++)
    {
        pwe[i]=pwe[i-1]*p;
        sum[i]=sum[i-1]*p+(s[i]-'a'+1);
    }
    int m;
    cin>>m;
   while(m--)
   {
        int l1,r1,l2,r2;
        scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
        ull t1=sum[r1]-sum[l1-1]*pwe[r1-l1+1];
        ull t2=sum[r2]-sum[l2-1]*pwe[r2-l2+1];
        if(t1==t2)printf("Yes\n");
        else printf("No\n");
    }
    return 0;
}

acwing139.回文子串的最大长度

枚举字符串每一个位置,二分向两边扩展的长度,对于枚举到的每一组左右两边的字符串比较它们的Hash值是否相等,注意要维护两个字符串前缀和数组,一个表示从左往右的前缀和,另一个表示从右往左,时间复杂度为O(nlogn),使用manacher算法可以把时间复杂度降到O(n)。

#include<iostream>
#include<cstring>
using namespace std;
typedef unsigned long long ull;
#define MAX_N 1000000
char s[MAX_N+5];
int p=131;
ull suml[MAX_N*2+5],sumr[MAX_N*2+5],pwe[MAX_N*2+5]={1};
int n;
int H(ull h[],int l,int r)
{
    return h[r]-h[l-1]*pwe[r-l+1];
}
bool check(int p,int l)
{
    if(H(suml,p-l,p-1)==H(sumr,n-(p+l)+1,n-(p+1)+1))return 1;
    return 0;
}
int main()
{
    int t=1;
    while(scanf("%s",s+1),strcmp(s+1,"END"))
    {

        n=strlen(s+1)*2;
        for(int i=n;i>0;i-=2)
        {
            s[i]=s[i/2];
            s[i-1]='z'+1;
        }
        for(int i=1,j=n;i<=n;i++,j--)
        {
            pwe[i]=pwe[i-1]*p;
            suml[i]=suml[i-1]*p+(s[i]-'a'+1);
            sumr[i]=sumr[i-1]*p+(s[j]-'a'+1);
        }
        int ans=0;
        for(int i=1;i<=n;i++)
        {
            int l=0,r=min(i-1,n-i),mid;
            while(l<r)
            {
                mid=(l+r+1)>>1;
                if(check(i,mid))l=mid;
                else r=mid-1;
            }
            if(s[i-l]<='z')ans=max(ans,l+1);
            else ans=max(ans,l);
        }
        cout<<"Case "<<t++<<": "<<ans<<endl;
    }
    return 0;
}

acwing140.后缀数组

最外层是快速排序sort,关键是cmp的设计。如果对每一对字符串的字典序进行比较,时间复杂度为O(n),效率较低。可以预处理字符串Hash,之后可以做到二分查找每一对字符串前缀的位置,时间复杂度为O(logn),找到之后比较后面的一个位置便可以直接比较出两个字符串的字典序,总体时间复杂度为O(nlognlogn)。
对于第二部分求相邻两个位置的前缀长度,可以直接调用前面提到的求前缀长度的函数。

#include<iostream>
#include<algorithm>
#include<limits.h>
#include<cstring>
using namespace std;
typedef unsigned long long ull;
#define MAX_N 300000
char s[MAX_N+5];
ull sum[MAX_N+5],pwe[MAX_N+5]={1};
int ind[MAX_N+5];
int p=131,len;
ull get(int l,int r)
{
    return sum[r]-sum[l-1]*pwe[r-l+1];
}
int get_qz(int a,int b)
{
    int l=0,r=len-max(a,b)+1,mid;
    while(l<r)
    {
        mid=(l+r+1)>>1;
        ull q1=get(a,a+mid-1),q2=get(b,b+mid-1);
        if(q1==q2)l=mid;
        else r=mid-1;
    }
    return l;
}
bool cmp(int a,int b)
{
    int t=get_qz(a,b);
    int l=(a+t>len?INT_MIN:s[a+t]),r=(b+t>len?INT_MIN:s[b+t]);
    return l<r;
}
int main()
{
    scanf("%s",s+1);
    len=strlen(s+1);
    for(int i=1;i<=len;i++)
    {
        sum[i]=sum[i-1]*p+(s[i]-'a'+1);
        pwe[i]=pwe[i-1]*p;
        ind[i]=i;
    }
    sort(ind+1,ind+1+len,cmp);
    for(int i=1;i<=len;i++)
    printf("%d ",ind[i]-1);
    cout<<endl<<"0 ";
    for(int i=2;i<=len;i++)
    printf("%d ",get_qz(ind[i-1],ind[i]));
    return 0;
}
  • 18
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
算法竞赛进阶指南》是一本进阶级别的书籍,不适合初学者阅读。根据引用中的描述,每一章都会总结书中的难点知识,并附上例题和习题。从引用的目录可以看出,《算法竞赛进阶指南》包含了基本算法、位运算、递推与递归、前缀和与差分、二分、排序、倍增、贪心等内容,还包括了基本数据结构如栈、队列、链表、Hash、字符串、Trie、二叉堆等。此外,书中还讲解了动态规划的各种子领域,如线性dp、背包、区间dp、树形dp、状态压缩dp等。对于想要深入学习算法竞赛的读者来说,《算法竞赛进阶指南》是一本很好的参考书籍。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [【算法竞赛进阶指南】学习笔记](https://blog.csdn.net/cpp_juruo/article/details/122520206)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] - *3* [算法竞赛进阶指南总结(一)](https://blog.csdn.net/weixin_64393298/article/details/124234703)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值