本专题的要求是熟练掌握这三个算法。能够灵活运用它们,并且可以修改它们。
K - Clairewd’s message
一开始理解错题意了,以为给的串中有多个暗码和明码,于是一点头绪都没有,看了题解之后才知道只有一个暗码和明码,那么做法就很显然了,先把串通过给的映射变换一下,那么它就变成明码+乱七八糟的东西了,然后再拓展kmp就行了。
#include
using namespace std;
char tab[30];
const int maxn=100005;
char s[maxn],_s[maxn];
int Next[maxn],extend[maxn];
void preEkmp(char s[],int slen)
{
Next[0]=slen;
int j=0;
while(j+11])j++;
Next[1]=j;
int k=1;
for(int i=2;iint p=k+Next[k]-1;
int L=Next[i-k];
if(i+L-1else
{
j=max(0,p-i+1);
while(i+jvoid Ekmp(char _s[],int _slen,char s[],int slen)
{
preEkmp(_s,_slen);
int j=0;
while(j<_slen&&_s[j]==s[j])j++;
extend[0]=j;
int k=0;
for(int i=1;iint p=k+extend[k]-1;
int L=Next[i-k];
if(i+L-1else
{
j=max(0,p-i+1);
while(i+jint main()
{
int T;
scanf("%d",&T);
while(T--)
{
char tmp[30];
scanf("%s",tmp);
for(int i=0;i<26;i++)
tab[tmp[i]-'a']=i+'a';
scanf("%s",s);
int slen=strlen(s);
memset(_s,'\0',sizeof(_s));
for(int i=0;i'a'];
Ekmp(_s,slen,s,slen);
bool flag=true;
for(int i=(slen+1)/2;iif(extend[i]+i==slen)
{
flag=false;
printf("%s",s);
for(int j=extend[i];jprintf("%c",_s[j]);
break;
}
}
if(flag)
{
printf("%s%s",s,_s);
}
puts("");
}
return 0;
}
N - String Problem
这题关键在于如何求出最小和最大的串,这里就有一个小知识点,字符串的最小表示法。该算法可以通过O(n)计算出最小的串。我会在另一篇博客详细讲解该算法。
#include
using namespace std;
const int maxn=1000005;
char s[maxn*2];
int Findmin(char s[],int len)
{
int i=0,j=1;
int k=0;
int t;
while(iif(!t)k++;
else
{
if(t>0)i+=k+1;
else j+=k+1;
if(i==j)j++;
k=0;
}
}
return min(i,j);
}
int Findmax(char s[],int len)
{
int i=0,j=1;
int k=0;
int t;
while(iif(!t)k++;
else
{
if(t<0)i+=k+1;
else j+=k+1;
if(i==j)j++;
k=0;
}
}
return min(i,j);
}
char tmp[maxn];
int Next[maxn];
void preKmp(char a[],int alen)
{
Next[0]=-1;
int i=0,j=-1;
while(iwhile(j!=-1&&a[i]!=a[j])j=Next[j];
Next[++i]=++j;
}
}
int Kmp(char a[],int alen,char b[],int blen)
{
preKmp(a,alen);
int i=0,j=0;
int ans=0;
while(i1)
{
while(j!=-1&&b[i]!=a[j])j=Next[j];
++i,++j;
if(j>=alen)
{
ans++;
}
}
return ans;
}
int main()
{
while(~scanf("%s",s))
{
int slen=strlen(s);
int miidx=Findmin(s,slen);
int maidx=Findmax(s,slen);
memset(tmp,'\0',sizeof(tmp));
for(int i=0;ifor(int i=0;i2]='\0';
printf("%d %d ",miidx+1,Kmp(tmp,slen,s,2*slen));
for(int i=0;iprintf("%d %d\n",maidx+1,Kmp(tmp,slen,s,2*slen));
}
return 0;
}
S - Finding Palindromes
本专题最好的一道题。
思路是不可能有的,只能看题解,才能做出来这样子。
如何判断一个串和另一个串连起来是否是回文串呢?这里有一个技巧。
设现在有串a和b,令len(a)< len(b),如果a+b是一个回文串的话,那么a的前缀必然等于b的反串的前缀,而b剩下的子串也必然是回文串。
如果b+a是回文串的话,那么b的前缀必然等于a的后缀,b剩下的子串必然是回文串。
我们现在来建立一颗字典树,将所有串的反串放进去,然后拿串a和字典树去匹配。如果a已经匹配完了,但字典树中a下面还有节点,那么我们需要知道a下面的节点所组成的串是不是回文串(第一种情况)。
如果a匹配到一个节点是一个串的终止位置,那么我们需要知道a后面的节点是否是回文串(第二种情况)。
所以现在的问题是如何知道一个串的后缀是否是回文串,这里用到了拓展kmp,只需要将串和他的反串进行拓展kmp,然后判断一下就知道后缀是否是回文串了。
根据上述讲解,字典树需要维护该节点是否是串的终止位置,和该节点后面有多少个回文串。
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn=2000005;
char s[maxn];
int Begin[maxn];
char tmp[maxn],tmp2[maxn];
int Next[maxn];
int extend[maxn];
void preEkmp(char a[],int len)
{
Next[0]=len;
int j=0;
while(j+1<len&&a[j]==a[j+1])j++;
Next[1]=j;
int k=1;
for(int i=2; i<len; i++)
{
int p=k+Next[k]-1;
int L=Next[i-k];
if(i+L-1Next[i]=L;
else
{
j=max(0,p-i+1);
while(i+j<len&&a[j]==a[j+i])j++;
Next[i]=j;
k=i;
}
}
}
void Ekmp(char a[],int alen,char b[],int blen)
{
preEkmp(a,alen);
int j=0;
while(jNext[0]=j;
int k=0;
for(int i=1; iint p=k+extend[k]-1;
int L=Next[i-k];
if(i+L-1else
{
j=max(0,p-i+1);
while(j+iint cnt,root;
int nxt[maxn][27];
int End[maxn],val[maxn];
int Newnode()
{
cnt++;
memset(nxt[cnt],-1,sizeof(nxt[cnt]));
End[cnt]=val[cnt]=0;
return cnt;
}
void Insert(char s[],int len)
{
int now=root;
for(int i=0; i<len; i++)
{
int go=s[i]-'a';
if(nxt[now][go]==-1)
nxt[now][go]=Newnode();
if(extend[i]+i==len)
val[now]++;
now=nxt[now][go];
}
End[now]++;
}
int query(char s[],int len)
{
int now=root;
ll res=0;
int i;
for(i=0; i<len; i++)
{
int go=s[i]-'a';
int id=nxt[now][go];
if(id==-1)break;
if(End[id]!=0)
{
if(i+1==len||extend[i+1]+i+1==len)
res+=End[id];
}
now=id;
}
if(i==len)
res+=val[now];
return res;
}
int main()
{
int n;
scanf("%d",&n);
int now=0;
int len;
for(int i=1; i<=n; i++)
{
scanf("%d %s",&len,s+now);
Begin[i]=now;
now+=len;
}
Begin[n+1]=now;
root=Newnode();
for(int i=1; i<=n; i++)
{
now=0;
for(int j=Begin[i+1]-1; j>=Begin[i]; j--)
tmp[now++]=s[j];
tmp[now]='\0';
now=0;
for(int j=Begin[i];j1]; j++)
tmp2[now++]=s[j];
tmp2[now]='\0';
len=Begin[i+1]-Begin[i];
Ekmp(tmp2,len,tmp,len);
Insert(tmp,len);
}
ll ans=0;
for(int i=1; i<=n; i++)
{
now=0;
for(int j=Begin[i+1]-1; j>=Begin[i]; j--)
tmp[now++]=s[j];
tmp[now]='\0';
now=0;
for(int j=Begin[i]; j1]; j++)
tmp2[now++]=s[j];
tmp2[now]='\0';
len=Begin[i+1]-Begin[i];
Ekmp(tmp,len,tmp2,len);
ans+=query(tmp2,Begin[i+1]-Begin[i]);
}
cout<
本来用结构体来存储的,结果mle了。。改成直接数组就过了。学到了一个减少内存的方法了。
Y - Theme Section
求一个形如EAEAE的串E的长度,拓展kmp后枚举判断一下即可。
#include
using namespace std;
const int maxn=1000005;
char str[maxn];
int Next[maxn];
void preEkmp(char s[],int len)
{
Next[0]=len;
int j=0;
while(j+1<len&&s[j]==s[j+1])j++;
Next[1]=j;
int k=1;
for(int i=2;i<len;i++)
{
int p=k+Next[k]-1;
int L=Next[i-k];
if(i+L-1Next[i]=L;
else
{
j=max(0,p-i+1);
while(j+i<len&&s[j]==s[j+i])j++;
Next[i]=j;
k=i;
}
}
}
bool check(int idx)
{
int len=strlen(str);
len=len-idx;
for(int i=len;i+len-1if(Next[i]>=len)return 1;
return 0;
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%s",str);
int len=strlen(str);
preEkmp(str,len);
int first=len/3;
first=len-first;
int ans=0;
for(int i=first;i<len;i++)
{
if(Next[i]+i==len&&check(i))
{
ans=Next[i];
break;
}
}
cout<