2022.2.14 本周题解

(1).【模板】KMP字符串匹配 - 洛谷

题目要求输入两行字符串,第一行字符串为主串。需要在主串中查找能与第二行字符串完全匹配的子串出现的位置,并输出每个位置上的最长相等前缀长。

一道标准的模板题,先定义分析子串前后缀相似度的函数“get_next()”,找出每个位置上的前后缀相似度的值并赋给其next[]数组,而后再在“index_kmp()”函数中进行字符串匹配。

按题目输出要求,在查找每个位置的最大前后缀相似度时用一个数组存储相似度,字符串匹配时,若匹配成功则可直接输出匹配首位置。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
char s[1000100],t[1000100];
int ls,lt,next1[1000100],nextval[1000100];
/*通过计算返回子串t的next数组*/
void get_next()
{
    int i=0,k=-1;
    next1[0]=-1;
    while(i < lt)/*此处t【0】表示串t的长度*/
    {
        if(k == -1 || t[i] == t[k])
        {
            ++i;++k;
            nextval[i]=k;
            if(t[i] != t[k])/*若当前字符与前缀字符不同*/
            next1[i]=k;/*则当前的k为next[i]的值*/
            else next1[i]=next1[k];/*若与前缀字符相同,则将前缀字符的next[]值赋给i位置上的next[]*/
        }
        else k=next1[k];/*若字符不同则k值回溯*/
    }
}
/*返回子串t在主串s中第pos个字符之后的位置,若不存在则返回值为0*/
/*t非空,1<=pos<=strlength(s)*/
void index_kmp()
{
    int i=0,j=0;
    get_next();/*对串t分析,得到next数组*/
    while(i < ls && j < lt)/*当i小于s长度同时j小于t的长度时,循环继续*/
    {
        if(j == -1 || s[i] == t[j])/*两串字符匹配则继续*/
        {++i;++j;}
        else j=next1[j];/*指针后退重新匹配,j退回合适的位置,i不变*/
        if(j == lt)
        {
             cout<<i-lt+1<<endl;
            j=next1[j];/*j值回溯到合适的位置*/
        }
    }
}
int main()
{
    cin>>s>>t;
    ls=strlen(s);lt=strlen(t);
    index_kmp();
    for(int i=1 ; i <= lt; i++ )
    cout<<nextval[i]<<" ";
    system("pause");
    return 0;
}

2).于是他错误的点名开始了 - 洛谷

很明显的一道hash类题目,为了避免暴力循环后的时间超限,我们将每个同学的名字视作键值,利用关联容器“map”将名字(字符串类型)与整型建立联系,这样就可以做到:将字符串作为数组下标计算。

将没有报到名字的同学标记为1,叫到了的标记为2,按要求输出即可。
 

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
using namespace std;
string k;
int n,m;
map<string,int>map_name;
int main()
{
    cin>>n;
    int i,j;
    for(i=1;i<=n;i++)
    {
        cin>>k;
        map_name[k]=1;/*将同学的名字标记为1*/
    }
    cin>>m;
    for(i=1;i<=m;i++)
    {
        cin>>k;
        if(map_name[k] == 2)
            cout<<"REPEAT"<<endl;
        else
        {
            if(map_name[k] == 1)
            {
                map_name[k]++;
                cout<<"OK"<<endl;
            }
            else cout<<"WRONG"<<endl;
        } 
 
    }
    system("pause");
    return 0;
}

(3).A-B 数对 - 洛谷

题目要求输入n个数,从中找出任意两个数相减可以得到定值C的组数。

我们先用哈希将所有输入的数进行标记,数值相同的则标记+1,最后遍历查找“每个元素+c”的标记个数,因为“每个元素+c”-"每个元素"可以得到定值c,最后的个数就是答案了。

这里我用map进行哈希,使每个输入的数字都进行一次哈希计算,保证不同的值一定会有不同的键值。
 

#include<iostream>
#include<cstdio>
#include<map>
using namespace std;
map<long long int,long long int>map_cut;
long long int n,c,a[1000100],ans=0;
int main()
{
    cin>>n>>c;
    int i,j;
    for(i=1;i<=n;i++)
    {
        cin>>a[i];
        map_cut[a[i]]++;/*此处表示数组map_cut以下标与键值联系,数组数值为相同键值个数,这样可以达到
        (不同位置的数字一样的数对算不同的数对)的要求*/
    }
    for(i=1;i<=n;i++)/*要求a-b=c,已知c为定值,那么a=b+c,即从a[1]开始遍历,每个遍历到的元素都视作b,那么求出a的个数就可以了*/
    ans+=map_cut[a[i]+c];/*答案=相差为c的数的个数的总和,即a[num[i]+c]位置的数的个数*/
    cout<<ans<<endl;
    system("pause");
    return 0;
}

(4).【模板】字符串哈希 - 洛谷 

这题和 “ 于是他错误的点名开始了 - 洛谷 ” 的做题思路几乎没差。用Map对所有输入的数据进行哈希计算,标记,遇到未重复的数据则使总数+1,最后得到的总数即答案。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
using namespace std;
map<string,int>standard;
int n,ans=0;
string k;
int main()
{
    int i,j;
    cin>>n;
    for(i=1;i<=n;i++)
    {
        cin>>k;
        if(standard[k] != 1)
        {
        standard[k]=1;
        ans++;
        }
    }
    cout<<ans<<endl;
    system("pause");
    return 0;
}

(5).[USACO09OCT]Barn Echoes G - 洛谷

题目给出两个字符串,要求分别比较某个字符串的前缀与另一个字符串的后缀相似度以及其后缀与另一个字符串前缀的相似度大小。

同样是用map,由于string类型的数据可以直接进行加减运算达到“拼接字符串”的目的,那么我们就可以设几个string变量用于一 一存储两个字符串的前后缀。

我设了一个string变量a来存储字符串s1的前缀,利用循环将字符串元素逐个加入,同时用map进行标记,然后再用string变量b来存储字符串s2的后缀,这里注意字符串相加的顺序即拼接的顺序,利用循环查找s1前缀与s2后缀的最大相似度。

再重复上面的操作来分析s1的后缀与s2的前缀相似度,得到最大相似度的值即答案。
 

#include<iostream>
#include<cstring>
#include<algorithm>
#include<map>
using namespace std;
map<string,int>map_s1head,map_s1end;
string s1,s2,a,b,c,d;
int sum;
int main()
{
    cin>>s1>>s2;
    int i,j;
    for(i=0;i<s1.size();i++)
    {
        a=a+s1[i];//a存储s1的前缀
        map_s1head[a]=i+1;//赋上s1的前缀长度
    }
    for(i=s2.size()-1;i>=0;i--)
    {
        b=s2[i]+b;//b存储s2的后缀,并与s1的前缀相比较、匹配
        sum=max(sum,map_s1head[b]);
    }
    for(i=s1.size()-1;i>=0;i--)
    {
        c=s1[i]+c;//c存储s1的后缀
        map_s1end[c]=s1.size()-i;//赋上s1的后缀长度
    }
    for(i=0;i<s2.size();i++)
    {
        d=d+s2[i];//d存储s2的前缀,并与s1的后缀相比较、匹配
        sum=max(sum,map_s1end[d]);
    }
    cout<<sum<<endl;
    system("pause");
    return 0;
}

(6).Compress Words - 洛谷

同样是看前后缀的题,受上一题启发,我原本打算还是用map的,然后发现可能map本身的时间复制度不低,即使我基本没有什么嵌套循环依然时间超限,算了,只能写kmp算法的解法了。

这里思路一定要清晰而明确。

首先,判断当前输入单词要并入主串的长度时,一定是拿主串本身的后缀与新输入的单词的前缀进行匹配,而不是拿上一个输入的单词的后缀与新输入的单词的前缀匹配,不然很容易并入当前输入单词多余的部分。

其次,当主串长度远远超过当前输入单词长度或者当前输入单词长度远远超过主串长度时,没必要再拿主串或新单词整个遍历一遍,因为两段字符串重合部分最长不超过短的那段字符串长度,因此我们需要在每次将要对新单词进行kmp时先判断比较一下主串与新单词的长度大小。

(做出来了,再说起这题就感觉也不难,,事实上花了好长时间来着,,)

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<map>
using namespace std;
int n,la,lb;
char a[1000010],b[1000010];
int next1[1000010];
int kmp(int pos)/*参数传递*/
{
    int c,k;/*先找出当前分析的字符串的各个位置上的最大前后缀相似度的值
	相当于将函数“get_next()”放入函数"kmp"中*/
    c=0;
    k=-1;
    next1[0]=-1;
    while(c<lb)
    {
        if(k==-1||b[c]==b[k])
        {++k;++c;
         next1[c]=k;}
        else k=next1[k];//若字符不同,则k值回溯
    }
    int i=pos;
    int j=0;
    while(i<la)
    {
        if(j==-1||a[i]==b[j])
        {++i;++j;}
        else
        j=next1[j];
        if(j==lb)
        return lb;
    }/*循环结束,表示此时两字符串匹配结束,得到第一个不匹配字符的位置*/
    return j;/*传递位置*/
}
int main()
{
    cin>>n>>a;/*第一个输入的单词暂时视作主串,后续会随着输入单词的增加而加长*/ 
    la=strlen(a);
    int i,j;
    for(i=1;i<n;i++)
    {
    cin>>b;/*每次输入更新b字符串*/
    lb=strlen(b);
    int pos=kmp(max(0,la-lb));/*kmp(max(0,la-lb))表示从主串的la-lb位置上开始遍历、匹配,用于删去多余的遍历判断操作,
	减小时间复杂度*/
    for(j=pos;j<lb;j++)
    a[la++] = b[j];
    }
    cout<<a<<endl;
    system("pause");
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值