学习记录18

上午

题目如下:

kmp的模板题,因为next数组的值没有设定好,导致求最长border的时候出现问题。后来修改了next的值的初始设定,然后所谓的border就是直接输出next数组里的值就好了。

就像之前说过的,kmp利用字符串的对称性来节省时间复杂度,next数组中的值其实就是当两字符串进行字符匹配的时候,如果失配了,该从哪个下标位置重新开始匹配。而我们的next值是根据i字符前所构成的字符串中前后缀相同的个数,比如:abab,此时next[4]=2(下标从0开始)。使用kmp的话,当abab后的那个字符失配了,我们就直接从下标为2的字符开始匹配,而不会是再接着之前初始的位置的下一个,这样就节省了不少时间复杂度。为什么呢?你想,既然已经k到了4了,代表之前的字符都是匹配成功的,并且这匹配成功的字符串还有前后缀相同的情况,这表明前后缀也是匹配成功的,那么接着下面去匹配也不必要了,因为之前的样子是符合匹配的,如果更改了另一个字符去匹配,那么一定是错误的,就可以省略了。直接从此副字符串中的中间位置去匹配,这也是因为字符串的对称性!!

代码如下:
 

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
int main()
{
    char s1[1000010],s2[1000010];//存储主串和匹配的子串
    scanf("%s%s",s1,s2);
    int next[1000010]={0,0};//第一个字符没有前后缀所以为0,第2个字符如果不匹配的话也只能回到第一个
                                //也就是下标为0,所以先给它赋值
    int l1=strlen(s1),l2=strlen(s2),k=0;
    for(int i=1;i<l2;i++)
    {
        while(k&&s2[i]!=s2[k])//k==0的时候直接跳过,当遇到两字符不相同时,就可以使用next数组中的值进行下标调整
                                //直到有字符相同才跳出
            k=next[k];
        next[i+1]=s2[i]==s2[k]?++k:0;//当有字符相同时,我们就可以把当前最长的前后缀相同字符串长度记下
    }
    k=0;
    for(int i=0;i<l1;i++)
    {
        while(k&&s1[i]!=s2[k])//开始和主串匹配,如果不匹配就使用next数组调整下标
            k=next[k];
        k+=s1[i]==s2[k]?1:0;//如果匹配就继续下一个字符的匹配
        if(k==l2)//当匹配完成后,输出相同的初始位置
            cout <<i-l2+2<<endl;
    }
    int i;
    for(i=1;i<l2;i++)//这就是所求的i长度的最长前后缀相同个数
        cout <<next[i]<<" ";
    cout <<next[i];
}

下午

题目为:

 一开始就想着用hash,因为看着这题目意思用哈希会挺简单的,实际上确实是这样,只是我之前钻了个牛角尖,导致写半天都是:

 为什么咧?因为我用map数组记录hash值的出现次数,这就导致得要一个很大很大的数组才能把hash给记录起来,然后就内存过大了……把数组调小一点的话,就变成上面那样了。我想应该是hash值出现了冲突,然后我就一直在修改,改啊改啊,改了半天没改出来,一直在说的哪两种情况徘徊。后来我看到题目说的测试例数也就10000个,我就直接开个unsigned long long的数组,没出现一个hash值就存进去,这是map也就只需要开10000个就好了。nice!然后就过了。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
#define ull unsigned long long int
ull an[10010],mapi[10010];//之前是想要开一个极大的数组用以存储hash值出现,就类似桶排
							//然后就卡了我两个小时……数组开大了就内存超限,开小点hash值就会出                        
                            //现冲突
							//真是裂开,后来我看了看总共也就会出现10000个人名,所以就算我一个            
                            //一个比较没多少时间复杂度
							//于是我干脆直接开一个这样大小的数组,类型改大就可以存储了,更何 
                             //况还可以自动溢出,mapi数组用以记录人名的出现次数
ull BKDRhash(char s[55])//BKDRhash函数转换
{
    int base=1313,len=strlen(s);
    ull hashi=0;
    for(int i=0;i<len;i++)
        hashi=(hashi*base+s[i]-'a');
    return hashi;
}
int main()
{
   int n,m;
   char st[55],l;
   cin >>n;
   l=getchar();
   for(int i=0;i<n;i++)
   {
       cin >>st;
       an[i]=BKDRhash(st);
       mapi[i]=1;//出现了的人名就给它标记
   }
   cin >>m;
   l=getchar();
   for(int i=0;i<m;i++)
   {
       cin >>st;
       ull t=BKDRhash(st);
       int j;
       for(j=0;j<n;j++)//在已经得出n个hash值中寻找是否已经出现此hash值
       					//已经出现就代表
        if(an[j]==t)
            {
            if(mapi[j]==1)//等于1代表有这个人名并没有重复点名
                cout <<"OK"<<endl,mapi[j]++;
            else if(mapi[j]>=2)//当等于2时,就说明已经点过一次名字了
                cout <<"REPEAT"<<endl;
            break;
            }
    if(j>=n)//此时从头到尾都没找到过此hash值的出现,说明没有这个人
        cout <<"WRONG"<<endl;
   }
}

晚上

接着思考kmp的原理和实现代码。

写题解。写博客。

看大话数据结构。

明日计划

写完最后一题,然后看最短路。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值