1.
当看到这一题的时候,我不带脑子抬手就做,
这个题目乍一看就是用暴力搜索,我开始想的是用一个一个的比的方式,遇到一样的字母就往后移,遇到不同的字母就整体向后移一位,然后花费大量时间写出了一个暴力代码,果然事情没那么简单,妥妥的时间超限了。
然后我就仔细看了一下这个题的标题
KMP字符串匹配
我悟了,这是要自学新算法的节奏,于是我去找到那本大话数据结构搁那磨磨唧唧,发现串和串的基本知识都能懂,但就是kmp这个知识点不进脑,我又秉持着不看别人的代码的毛病,结果白忙活了。
果然啥事开头难,我又又又又卡在了学习新知识的开头
然后我上网看一些up讲了这个知识点,对于这个题我们要先通过s2字符串建立一个前缀和与后缀字符最大相同情况的线性表,
我们需要一个一个逐后比较此时前后缀的最大相同的字母个数,我们需要定义两个数用来确认此时正在比较的字符下标和通过上一次比较后字符数组内最大前缀的末尾,这里有个优化就是当我们要在表中填数字的时候,我们可以直接判断新增的字符是不是和之前最大前缀的末尾加一是不是相同,然后只需要比较长串和短串就行了,运用对称的原理,我们引出两个指针,一个指向当前长串的末尾,还有一个指针指向短串的当前最大相等长度,所以当遇到字符不相等的时候,我们可以跳转至短串的最长前后缀相同的前缀末尾,以至于减小时间复杂度今天就只需要比较长串和短串就行了,运用对称的原理,我们引出两个指针,一个指向当前长串的末尾,还有一个指针指向短串的当前最大相等长度,所以当遇到字符不相等的时候,我们可以跳转至短串的最长前后缀相同的前缀末尾,以至于减小时间复杂度
#include<stdio.h>
#include<string.h>
#define max 1000010
void prefix_table(char s2[],int prefix[],int n)//构建前缀与后缀字符最大的相同情况数组
{
prefix[0]=0;//当只有一个字母时 无前后缀之分
int len=0;//比较长度初始化为0
int i=1;//此时正在比较的字符的数组下标,其实也就是当前需开始的字符串的末尾
while(i<n)
{
if(s2[i]==s2[len])//遇到两个字母相等时
{
len++;//说明前后缀相同的长度增加了1
prefix[i]=len;//更新数据
i++;//继续比较下一位
}
else//遇到不相等的情况
{
if(len>0)//在前面就有字符相等的情况
len=prefix[len-1];
else//此时前一个和最后一个不相等
{
prefix[i]=0;
i++;
}
}
}
}
void fun(char s1[],char s2[],int prefix[],int m,int n)
{
int i=0,j=0;//一个位于长串,一个位于短串
while(i<m)
{
if(s1[i]==s2[j])//两字符相等,继续向下一位判断
{
i++;
j++;
}
else//两字符不相等
{
if(j>0)//如果不是在开头
{
j=prefix[j-1];
}
else//在开头的情况
i++;
}
if(j==n)//此时匹配成功一段子串
{
printf("%d\n",i-j+1);
j=prefix[j-1];//子串的指向向前移
}
}
}
int main()
{
char s1[max],s2[max];
scanf("%s%s",s1,s2);
int prefix[max];//前缀与后缀字符最大的相同情况数组
int n=strlen(s2),m=strlen(s1);//字符串的长度
prefix_table(s2,prefix,n);//构建前缀与后缀字符最大的相同情况数组
fun(s1,s2,prefix,m,n);//判断长串与短串的相等情况
for(int i=0;i<n;i++)
printf("%d ",prefix[i]);
return 0;
}
2.
C++的STL库map用法
首先map是一种映射,可以当作特殊数组使用,在使用时先要引入一个头文件#include,然后基本表示方法是map a(类型名);例:map a,名称为a的由整形对应一个字符型的一个映射,类比于数组的下标和值,所以数据都是成对出现
一般都是一般操作有:
插入:a[10]=‘a';键为10值为a
查找:a.count(10),查找map中有没有键为10的一个元素,如果有这个元素返回值为1否则为0;
删除:a.erase(10),删除这个元素
计数:a.size(),一共有多少个元素
综上所述,map是一个另类数组,他所谓的“下标”可以是任何数据,所以我们就可以构建一个一string类型为“下标”,值为int的map,通过判断下标的存在可以得出这个人是不是在班上,然后string所映射的值可以为此人被念过做标记,当没念过的时候为1,念过之后就为2。3.
#include<iostream>
#include<map>
using namespace std;
int main()
{
int n,m;
string st;
map<string,int> mp;//建立一个由string对应的整形map.
cin>>n;
for(int i=1; i<=n; i++)
{
cin>>st;
mp[st]++;//插入一个键为st的元素
}
cin>>m;
for(int i=1; i<=m; i++)
{
cin>>st;
if(mp[st]==1)//当map中有这个人名时
{
cout<<"OK"<<endl;
mp[st]++;//标记这个元素已经被点过了
}
else if(mp[st]==2)//当遇到点过的元素
cout<<"REPEAT"<<endl;
else
cout<<"WRONG"<<endl;//当点到一个错误的名字
}
return 0;
}
3.
所谓字符串哈希就是构造一个数字使之唯一代表一个字符串,进制要大于每一个字符对应的数字
#include<bits/stdc++.h>
#include<string.h>
using namespace std;
typedef unsigned long long ULL; //哈希值可能非常大
ULL haxi(char st[])
{
long long int temp=0;
for(int i=0;i<strlen(st);i++)
temp=(temp*29+(st[i]-'a'+1))%212370440130137957l;//将字符串转换为特定的数据
return temp;
}
int main()
{
int n,sum=1;
ULL a[10009];
char st[50000];
cin>>n;
for(int i=0; i<n; i++)
{
scanf("%s",st);
a[i]=haxi(st);///将字符串转化为一个特定的数字
}
sort(a,a+n);//排序
for(int i=1;i<n;i++)
{
if(a[i]!=a[i-1])//当遇到前后不同的数据时
sum++;//计数器加一
}
cout<<sum;
}
4.
我仔细看了一下题,题中给定了数据的最大值,没给最小值,也就是说数据有可能是负数,但是数组下标不可能是负数,所以应用了一个map类型
#include<bits/stdc++.h>
using namespace std;
int a[200005];
map<int,int> b;
int main()
{
long long int n,c,sum=0;
cin>>n>>c;
for(int i=0; i<n; i++)
{
cin>>a[i];
b[a[i]]++;//标记这个数字出现了几次
}
for(int i=0; i<n; i++)
{
sum=sum+b[a[i]+c];//当这个数字满足它数对的条件时加上自身ps:因为不同位置的数字一样的数对算不同的数对
}
cout<<sum;
return 0;
}
5.
substr函数,
一般用法:
a=s.substr(n,m):截取字符串 s 中从第 n位(位数从 00 开始)开始的长度为 m的字符串并赋给 a。
所以我们只需要构建两个string然后直接全部判断从第一个字符串的整体长度逐个递减,同时另一个字符串也从后往前取相同长度的字符串(注意长度),然后进行判断是否是一致的,再分别讨论短串前缀和长串后缀和短串后缀和长串前缀的情况哪个长一些
#include<bits/stdc++.h>
using namespace std
int main()
{
string s1,s2;
cin>>s1>>s2;
int leth1=s1.size(),leth2=s2.size();//计算s1和s2的长度
int max1=0,max2=0,maxx;//最大相同值为0
int mm=min(leth1,leth2);//得出较小长度,以防越界
for(int i=mm;i>=1;i--)//先计算s1的前缀和s2的后缀最大相同情况
{
string ss1=s1.substr(0,i);//从第一个字符串里取出0~0+i位置的字串
string ss2=s2.substr(leth2-i,i);从第二个字符串里取出末尾-i~末尾位置的字串
if(ss1==ss2)//当s1的前缀和s2的后缀相同时
{
max1=i;
break;//因为是从大到小依次减少所以直接循环结束
}
}
for(int i=mm;i>=1;i--)//计算s1的后缀和s2的前缀最大相同情况
{
string ss1=s1.substr(leth1-i,i);//从第一个字符串里取出0~0+i位置的字串
string ss2=s2.substr(0,i);从第二个字符串里取出末尾-i~末尾位置的字串
if(ss1==ss2)//当s1的前缀和s2的后缀相同时
{
max2=i;
break;//因为是从大到小依次减少所以直接循环结束
}
}
maxx=max(max1,max2);
cout<<maxx;
}
6.
这个题的大概意思就是先你一个起始串,然后逐个往后加新的串,并且前一个串的后缀和新串的前缀冲突,也就是最大长度的消去新串的前缀与前一个串相同的后缀部分。
首先考虑的是哈希,运用哈希我们直接暴力解,一个一个判断当后缀为1时前缀与后缀取相同值然后再一同增加直到不在相等,或者长度比较短的那个串长了就停下
#include<bits/stdc++.h>
using namespace std;
int maxn=100009;
int base=2333333;
int mod=999999998;
string ans,s;//一个是开始串,一个是后面要加进来的串
void haxi()
{
long long int h1=0,hr=0,temp=1,index=0;
int leth=min(ans.size(),s.size());//取开始串和要合并的串的长度较小值
for(int i=0;i<leth;i++)
{
h1=(h1*base+ans[ans.size()-i-1])%mod;//获取左边数组的后缀Hash值
hr=(s[i]*temp+hr)%mod;//获取右边数组的前缀Hash值
temp=(temp*base)%mod;
if(h1==hr)
index=i+1;//获取最大的字符匹配位数
}
for(int i=index;i<(int)s.size();i++)
ans=ans+s[i];
}
int main()
{
int n;
cin>>n>>ans;
for(int i=1;i<n;i++)
{
cin>>s;
haxi();
}
cout<<ans;
return 0;
}
我觉得很奇怪,始终认为代码没什么问题,而此时题组就要截至,我绞劲脑汁都没想明白这是咋回事,然后我去看了别人与我的区别,就该动了一个地方
我的:
ans=ans+s[i];
大佬的:
ans += s[i];
就过了
这辈子没见过这么离谱的事,我现在都没整明白到底有什么不同,可恶啊