POJ 1200 Crazy Search 哈希映射

Many people like to solve hard puzzles some of which may lead them to madness. One such puzzle could be finding a hidden prime number in a given text. Such number could be the number of different substrings of a given size that exist in the text. As you soon will discover, you really need the help of a computer and a good algorithm to solve such a puzzle.
Your task is to write a program that given the size, N, of the substring, the number of different characters that may occur in the text, NC, and the text itself, determines the number of different substrings of size N that appear in the text.

As an example, consider N=3, NC=4 and the text “daababac”. The different substrings of size 3 that can be found in this text are: “daa”; “aab”; “aba”; “bab”; “bac”. Therefore, the answer should be 5.
Input
The first line of input consists of two numbers, N and NC, separated by exactly one space. This is followed by the text where the search takes place. You may assume that the maximum number of substrings formed by the possible set of characters does not exceed 16 Millions.
Output
The program should output just an integer corresponding to the number of different substrings of size N found in the given text.
Sample Input
3 4
daababac
Sample Output
5
Hint
Huge input,scanf is recommended.

转载的一个
大佬的思路:

题的意思是:

给出N和NC,以及一个字符串,字符串中出现NC个不同字母,寻找到长度为N的子串的个数
3 4
daababac
N=3,NC=4,找出“daababac”中长度为3的子串,NC=4代表字符串中只包含‘d’、‘a’、‘b’、‘c’四种字符
子串包括:‘daa’、‘aab’、‘aba’、‘bab’、‘aba’、‘bac’,去掉一个重复的,结果为5个子串

一般思路:

依次查找每一个子串,并加入一个集合,加入集合时要对集合进行遍历,看看是否集合中已有,没有就加入并计数+1.最后得到计数结果。
问题:集合会很大,每次在集合中遍历也会很慢,肯定会超时
解决思路:对于每一个子串,能直接定位并确定以前是否已经有该子串
可以设计一个hash算法,让每一个子串对应于一个唯一的下标位置
长度为N的子串,且出现的字符只有NC种,可以将子串当初一个N位NC进制数。
例如 “daababac”中有 ‘d’、‘a’、‘b’、‘c’四种字符,可以分别定义为‘d’-0、‘a’-1、‘b’-2、‘c’-3,则“daababac”就是01121213
子串 四进制 十进制(hash的下标位置)
daa 011 5
aab 112 22
aba 121 25
bab 212 38
aba 121 25
bac 213 39
这样,每一个不同的子串都对应 一个唯一的hash值,该值可以作为下标。

具体思路

这里借鉴了2019寒假冬令营清华讲师尹昊萱的代码,需要的数据结构有:
字符串,字符数组 char s[1000010],存放整个字符串
hash数组:bool hash[16000010],如果找到一个子串,将对应hash值下标的hash[i]赋值为True,标明已有该子串,避免重复计数
计数变量ans: int ans ,记录已找到的不重复的子串个数

总体思路就是:对于s中的每一个长度为N的子串,计数其hash值,如果数组hash[ ]中对应元素是True,说明该子串已有,否则计数ans+1,同时将hash[ ]对应元素赋值为True

重点来了
要想实现上述思路,需要解决以下几个问题:
1 将每一个子串转换成一个NC进制数
可以对整个字符串进行处理,将其转换成一个NC进制数,因此,对于char s[1000010],需要一个int s1[1000010]存放转换后的NC进制数串
转换时,根据每个字符出现的顺序确定该字符对应的数字,例如字符串中出现的第一个字符“d”作为‘0’,第二个出现的字符“a”作为‘1’,第三个出现的字符‘b’作为‘2’……,因此还需要一个字符到数字的映射表map: int map[1010]。在map数组中,根据每个字符的ascii码值作为该字符的下标存放该字符对应的数字,如上,‘d’的ascii码是100,则map[100]=0,‘a’的ascii码是97,则map[97]=1,‘b’的ascii是98,则map[98]=2,‘c’的ascii码99,则map[99]=3。map[ ]的初始值为-1.

通过一次循环从头到尾处理字符串的每一个字符,一方面根据其出现的顺序确定其对应的数字并填入map中,另一方面同时将每一个字符对应的数字填入s1[ ]数组中,具体代码如下:

       cnt=0;				//cnt初值为0,第一个出现的字符对应为0,第二个出现的为1
       memset(map,0xff,sizeof(map));
       for (i=1;i<=l;i++)
       {																//对于每一个字符 s[i]
           if (map[s[i]]==-1) map[s[i]]=cnt++;		//如果该字符首次出现,cnt作为其对应的数字
           s1[i]=map[s[i]];				//同时将对应的数字存入数组s1[i]中,下标从1开始!
       }

2 将每个子串的NC进制数转换成十进制数
首先,将一个NC进制的数转换成十进制,需要知道每一位的权值,第n位的权值是NC的n-1次方,为了计算方便,可以将每一位的权值存放在一个数组中,该数组为int pow[10010]
生成该数组的代码:

        pow[0]=1;
        for (i=1;i<=k;i++)			//	k是子串的长度,如3
          pow[i]=pow[i-1]*p;			// p是NC,如4

当整个字符串已经转换成NC进制的数字串并存入s1[ ]中以后,先计算第一个子串对应的十进制数,然后通过一次循环,依次计算后续每一个子串对应的十进制数,并检查该十进制数对应的hash[ ]值是否为True,如果是True,则说明该子串已经出现过;如果是False,则计数ans+1,同时赋值为True。

    x=0;				//每次转换后的十进制数,作为hash数组的下标
    for (i=1;i<=k;i++)		//	这个循环计算第一个子串
      x=x*p+s1[i];
    hash[x]=1;
    ans=1;
    for (i=k+1;i<=l;i++)			//这个循环处理后续的每一个子串
    {
        x-=s1[i-k]*pow[k-1];	//在上一次计算的十进制值基础上,减掉最高位
        x*=p;							// 剩余位数 * NC
        x+=s1[i];						//	加上下一位,得到新的子串的十进制值
        if (!hash[x]) ans++;		//	
        hash[x]=1;					//
    }

完整代码:

#include<cstdio>
#include<cstring>
char s[1000010];
bool hash[16000010];
int pow[10010],s1[1000010],map[1010];
int main()
{
    int i,j,k,l,m,n,p,q,x,y,z,ans,cnt;
    while (scanf("%d%d",&k,&p)==2)
    {
        cnt=0;
        pow[0]=1;
        for (i=1;i<=k;i++)
          pow[i]=pow[i-1]*p;
        memset(hash,0,sizeof(hash));
        memset(map,0xff,sizeof(map));
        scanf("%s",s+1);
        l=strlen(s+1);
        if (l<k)
        {
            printf("0\n");
            continue;
        }
        for (i=1;i<=l;i++)
        {
            if (map[s[i]]==-1) map[s[i]]=cnt++;
            s1[i]=map[s[i]];
        }
        x=0;
        for (i=1;i<=k;i++)
          x=x*p+s1[i];
        hash[x]=1;
        ans=1;
        for (i=k+1;i<=l;i++)
        {
            x-=s1[i-k]*pow[k-1];
            x*=p;
            x+=s1[i];
            if (!hash[x]) ans++;
            hash[x]=1;
        }
        printf("%d\n",ans);
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值