给出两个串S T,A操作S,B操作T。A先手,两人必须选择一种操作:翻转 23000->32 、 /10。S包含T了A胜利。
思路:先将T串末尾的0去掉,然后用T去匹配S和S的反串。
#include <iostream>
#include <algorithm>
#include <stdio.h>
#include <string.h>
#include <queue>
#define maxs 2020202
#define mme(i,j) memset(i,j,sizeof(i))
using namespace std;
char s[1000005],s2[1000005];
int nexts[maxs];
void getn()
{
int len=strlen(s2);
int i=0,j;
j=nexts[0]=-1;
for(i=0;i<len;)
{
if(j==-1 || s2[i]==s2[j])
nexts[++i]=++j;
else
j=nexts[j];
}
}
bool kmp()
{
getn();
int len1=strlen(s),len2=strlen(s2);
int i=0,j=0;
for(i=0;i<len1;)
{
if(j==-1||s[i]==s2[j])
{
i++;
j++;
}
else
j=nexts[j];
if(j>=len2)
return 1;
}
if(j>=len2)
return 1;
return 0;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%s%s",s,s2);
int flag = 1;
for(int i=0;s2[i];i++){
if(s2[i]=='0') continue;
flag = 0 ;break;
}
int l2 = strlen(s2);
for(int i=l2-1;i>=0;i--){
if(s2[i]=='0')
s2[i]='\0';
else break;
}
if(flag||kmp())
{
printf("Alice\n");
}
else
{
reverse(s2,s2+strlen(s2));
if(kmp())
{
printf("Alice\n");
}
else printf("Bob\n");
}
}
}
给出两个数组,让你在任意一个数组中取任意一段连续子序列,让其是另外一个数组的子串,求这个合法子段的最长长度。
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<queue>
using namespace std;
struct node
{
int x,y;
}now,nex;
char mp[505][505];
int step[505][505];
int numa[505];
int numb[505];
int fx[4]={0,0,1,-1};
int fy[4]={1,-1,0,0};
int a[500];
int b[500];
int naxt[2500];
int conta,contb,n,m;
int lena,lenb;
int Bfs()
{
queue<node >s;
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
step[i][j]=0x3f3f3f3f;
if((i==0&&j==0)||(i==n-1&&j==m-1)||(i==n-1&&j==0)||(i==0&&j==m-1))
{
now.x=i;
now.y=j;
step[i][j]=0;
s.push(now);
}
}
}
while(!s.empty())
{
now=s.front();
s.pop();
for(int i=0;i<4;i++)
{
nex.x=now.x+fx[i];
nex.y=now.y+fy[i];
if(nex.x>=0&&nex.x<n&&nex.y>=0&&nex.y<m&&mp[nex.x][nex.y]!='#')
{
if(step[nex.x][nex.y]>step[now.x][now.y]+1)
{
step[nex.x][nex.y]=step[now.x][now.y]+1;
s.push(nex);
}
}
}
}
return 0;
}
void set_naxt()//子串的naxt数组
{
int i=0,j=-1;
naxt[0]=-1;
while(i<lenb)
{
if(j==-1||b[i]==b[j])
{
i++; j++;
naxt[i]=j;
}
else
j=naxt[j];
}
}
int KMP()
{
int maxnlen=0;
int i=0,j=0;
set_naxt();
while(i<lena)
{
if(j==-1||a[i]==b[j])
{
i++;j++;
maxnlen=max(j,maxnlen);
}
else
{
maxnlen=max(j,maxnlen);
j=naxt[j];
}
if(j==lenb)
{
return lenb;
}
}
return maxnlen;
}
int main()
{
while(~scanf("%d%d%d%d",&conta,&contb,&m,&n))
{
for(int i=0;i<n;i++)scanf("%s",mp[i]);
Bfs();
for(int i=0;i<conta;i++)
{
int x,y;
scanf("%d%d",&x,&y);
numa[i]=step[x][y];
}
for(int i=0;i<contb;i++)
{
int x,y;
scanf("%d%d",&x,&y);
numb[i]=step[x][y];
}
sort(numa,numa+conta);
sort(numb,numb+contb);
for(int i=0;i<conta;i++)
{
if(numa[i]==0x3f3f3f3f)
{
conta=i;
break;
}
}
for(int i=0;i<contb;i++)
{
if(numb[i]==0x3f3f3f3f)
{
contb=i;
break;
}
}
int output=0;
if(conta<contb)
{
lena=contb;
for(int i=0;i<contb;i++)
{
a[i]=numb[i];
}
for(int i=0;i<conta;i++)
{
lenb=0;
for(int j=i;j<conta;j++)
{
b[lenb++]=numa[j];
}
output=max(KMP(),output);
}
}
else
{
lena=conta;
for(int i=0;i<conta;i++)
{
a[i]=numa[i];
}
for(int i=0;i<contb;i++)
{
lenb=0;
for(int j=i;j<contb;j++)
{
b[lenb++]=numb[j];
}
output=max(KMP(),output);
}
}
printf("%d\n",output);
}
}
给出两个串S T,可以任意次使得T串减去X(>=0或<0)问有多少个修改过的T串出现在S串中
求出差值,然后进行KMP匹配
#include<stdio.h>
#include<string.h>
using namespace std;
int tmpa[2500050];
int tmpb[2500050];
int a[250050];
int b[250050];
int next[250050];
int n,m;
int lena;
int lenb;
int output;
void set_naxt()//子串的next数组
{
int i=0,j=-1;
next[0]=-1;
while(i<lenb)
{
if(j==-1||b[i]==b[j])
{
i++; j++;
next[i]=j;
}
else
j=next[j];
}
}
int kmp()
{
int i=0,j=0;
set_naxt();
while(i<lena)
{
if(j==-1||a[i]==b[j])
{
i++;j++;
}
else
j=next[j];
if(j==lenb)
{
output++;
j=next[j];
//printf("%d\n",j);
}
}
return -1;
}
int main()
{
while(~scanf("%d%d",&n,&m))
{
for(int i=0;i<n;i++)scanf("%d",&tmpa[i]);
for(int i=0;i<m;i++)scanf("%d",&tmpb[i]);
if(m==1)
{
printf("%d\n",n);
continue;
}
for(int i=0;i<n-1;i++)tmpa[i]=tmpa[i]-tmpa[i+1];
for(int i=0;i<m-1;i++)tmpb[i]=tmpb[i]-tmpb[i+1];
lena=n-1;
lenb=m-1;
for(int i=0;i<n-1;i++)a[i]=tmpa[i];
for(int i=0;i<m-1;i++)b[i]=tmpb[i];
output=0;
kmp();
printf("%d\n",output);
}
}
给出N个字符串,求最大的I使得在I之前至少有一个字符串不是I的子串
枚举第i个字符串,第二重设置个l,若有满足题意的情况直接跳出,否则l一直左移,因为若xj<i,且xj都是i的子串,那么不必再判断xj与i+1的关系
AA
AAA
AAAB
AAABBB讨论这个的时候,只需要和AAAB比较,不必和以前的比了。
#include<string.h>
#include<stdio.h>
using namespace std;
char aa[600][5000];
char a[200005];
char b[2005];
int next[2005];
int lena;
int lenb;
void set_naxt()//子串的next数组
{
int i=0,j=-1;
next[0]=-1;
while(i<lenb)
{
if(j==-1||b[i]==b[j])
{
i++; j++;
next[i]=j;
}
else
j=next[j];
}
}
int kmp()
{
int i=0,j=0;
set_naxt();
while(i<lena)
{
if(j==-1||a[i]==b[j])
{
i++;j++;
}
else
j=next[j];
if(j==lenb)
return 1;
}
return 0;
}
int main()
{
int t;
int kase=0;
scanf("%d",&t);
while(t--)
{
int n;
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%s",aa[i]);
}
int l=0;
int ans=-1;
printf("Case #%d: ",++kase);
for(int i=1;i<n;i++)
{
while(l<i)
{
strcpy(a,aa[i]);
strcpy(b,aa[l]);
lena=strlen(a);
lenb=strlen(b);
if(kmp()==1)
{
l++;
}
else
{
ans=i+1;
break;
}
}
}
printf("%d\n",ans);
}
}
给出S串和T串,求最少的分解使得分解的子串可以顺序或逆序出现在S中,并求位置。
暴力枚举T的子串,设置L=0 R=0 若在S顺序串或逆序串中匹配,其不断使得RR++。不能匹配后设置L=R,然后继续找下一个子串。
#include<stdio.h>
#include<algorithm>
#include<string.h>
using namespace std;
int nextt[40000];
char raa[40000];
char aa[40000];
char bb[40000];
char cc[40000];
char a[40000];
char b[4000];
int ans[400000][2];
int lena,lenb,contz,flagr,lenaa,lenbb;
void set_naxt()//子串的nextt数组
{
int i=0,j=-1;
nextt[0]=-1;
while(i<lenb)
{
if(j==-1||b[i]==b[j])
{
i++; j++;
nextt[i]=j;
}
else
j=nextt[j];
}
}
int KMP()
{
int i=0,j=0;
set_naxt();
while(i<lena)
{
if(j==-1||a[i]==b[j])
{
i++;j++;
}
else
j=nextt[j];
if(j==lenb)
{
if(flagr==0)
{
ans[contz][0]=i-lenb+1;
ans[contz][1]=i;
}
else
{
ans[contz][0]=lenaa-(i-lenb+1)+1;
ans[contz][1]=lenaa-i+1;
}
return 1;
}
}
return -1;
}
//b匹配a
int main()
{
while(~scanf("%s%s",raa,bb))
{
contz=0;
lenaa=strlen(raa);
lenbb=strlen(bb);
strcpy(aa,raa);
reverse(raa,raa+lenaa);
int ll=0;
int rr=0;
while(ll<lenbb)
{
int flag=0;
while(rr<lenbb)
{
strcpy(a,aa);
flagr=0;
int cont=0;
for(int j=ll;j<=rr;j++)
{
b[cont++]=bb[j];
}
b[cont]='\0';
lena=lenaa;
lenb=rr-ll+1;
if(KMP()==1)
{
flag=1;
rr++;
continue;
}
else
{
strcpy(a,raa);
flagr=1;
int cont=0;
lena=lenaa;
lenb=rr-ll+1;
for(int j=ll;j<=rr;j++)
{
b[cont++]=bb[j];
}
b[cont]='\0';
if(KMP()==1)
{
flag=1;
rr++;
continue;
}
else break;
}
if(flag==0)break;
}
if(flag==0)break;
contz++;
ll=rr;
}
if(ll==lenbb)
{
printf("%d\n",contz);
for(int i=0;i<contz;i++)
{
printf("%d %d\n",ans[i][0],ans[i][1]);
}
}
else printf("-1\n");
}
}
看A串是否包含B串
#include<stdio.h>
#include<string.h>
using namespace std;
char a[200005];
char b[2005];
int next[2005];
int lena;
int lenb;
void set_naxt()//子串的next数组
{
int i=0,j=-1;
next[0]=-1;
while(i<lenb)
{
if(j==-1||b[i]==b[j])
{
i++; j++;
next[i]=j;
}
else
j=next[j];
}
}
int kmp()
{
int i=0,j=0;
set_naxt();
while(i<lena)
{
if(j==-1||a[i]==b[j])
{
i++;j++;
}
else
j=next[j];
if(j==lenb)
return 1;
}
return 0;
}
int main()
{
while(~scanf("%s%s",a,b))
{
memset(next,0,sizeof(next));
lena=strlen(a);
lenb=strlen(b);
if(kmp()==1)printf("yes\n");
else printf("no\n");
}
}
统计所有前缀中字符长度的总和
#include<stdio.h>
#include<string.h>
#include<iostream>
using namespace std;
#define ll long long int
#define mod 10007
char a[1500000];
ll nex[1500000];
ll dp[1500000];
ll len;
void Set_next()
{
ll i=0,j=-1;
nex[0]=-1;
while(i<=len)
{
if(j==-1||a[i]==a[j])
{
i++,j++;
nex[i]=j;
}
else
j=nex[j];
}
}
int main()
{
int t;
cin>>t;
while(t--)
{
cin>>len;
scanf("%s",a);
memset(nex,0,sizeof(nex));
memset(dp,0,sizeof(dp));
len=strlen(a);
Set_next();
for(ll i=len; i>=1; i--)
{
dp[i]++;
dp[nex[i]]+=dp[i];
dp[nex[i]]%=mod;
dp[i]%=mod;
}
ll output=0;
for(ll i=1; i<=len; i++)
{
//cout<<dp[i]<<endl;
output+=dp[i];
output%=mod;
}
printf("%lld\n",output%mod);
}
}
输出所有前缀中字符长度与出现次数的乘积的最大值
设dpi状态,则 dpi++ dp[next[i]]+=dp[i]
#include<stdio.h>
#include<string.h>
#include<iostream>
using namespace std;
#define ll long long int
char a[1500000];
ll nex[1500000];
ll dp[1500000];
ll len;
void Set_next()
{
ll i=0,j=-1;
nex[0]=-1;
while(i<=len)
{
if(j==-1||a[i]==a[j])
{
i++,j++;
nex[i]=j;
}
else j=nex[j];
}
}
int main()
{
while(~scanf("%s",a))
{
memset(nex,0,sizeof(nex));
memset(dp,0,sizeof(dp));
len=strlen(a);
Set_next();
for(ll i=len;i>1;i--)
{
dp[i]++;
dp[nex[i]]+=dp[i];
}
ll output=0;
for(ll i=1;i<=len;i++)
{
output=max(output,dp[i]*i);
}
printf("%lld\n",output);
}
}
二维哈希
//核心思想:将字符串看成P进制数,P的经验值是131或13331,取这两个值的冲突概率低
//小技巧:取模的数用2^64,这样直接用unsigned long long存储,溢出的结果就是取模的结果
//如果超时改为unsigned int
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ULL;
const int N=1e3+5;
ULL hashv[N][N], power[N*N]; // hashv[k]存储字符串前k个字母的哈希值, power[k]存储 P^k mod 2^64
char str[N][N],str1[N];
int n,m,A,B;
int P;
// 初始化
void calHash()
{
P=131;
for (int i = 1; i <= n; i++ )
{
for(int j=1; j <= m; j++)
{
hashv[i][j] = hashv[i][j-1] * P + (str[i][j]-'0');
}
}
power[0] = 1;
for(int i=1; i<=n*m; i++)
{
power[i] = power[i - 1] * P;
}
}
// 计算子串 str[l ~ r] 的哈希值
ULL get(ULL f[],int l, int r)
{
return f[r] - f[l - 1] * power[r - l + 1];
}
int main()
{
scanf("%d%d%d%d",&n,&m,&A,&B);
for(int i=1; i<=n; i++)
{
scanf("%s",str[i]+1);
}
calHash();
//将大小为A*B的矩阵Hash保存到Hash中
unordered_set<ULL> Hash;
for(int i=B; i<=m; i++)
{
ULL s=0;
int l=i-B+1,r=i;
for(int j=1; j<=n; j++)
{
s=s*power[B]+get(hashv[j],l,r);
if(j-A>0)
s-=get(hashv[j-A],l,r)*power[A*B];
if(j>=A)
Hash.insert(s);
}
}
int Q;
scanf("%d",&Q);
while(Q--)
{
ULL s=0;
for(int i=1; i<=A; i++)
{
scanf("%s",str1+1);
for(int j=1; j<=B; j++)
{
s=s*P+str1[j]-'0';
}
}
if(Hash.count(s))
printf("1\n");
else
printf("0\n");
}
return 0;
}
求出所有的子串长度使得既是S的前缀也是S的后缀
利用NEXT数组,例如ABADABA 先求出NEXT[7]=3,然后NEXT3=1 都能满足条件。
不断递推NEXT数组即可
求出至少补充
#include <iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
using namespace std;
const int MAXN=400000+1000;
char P[MAXN];
int m,next[MAXN];
void getFail()
{
next[0]=next[1]=0;
for(int i=1;i<m;i++)
{
int j=next[i];
while(j && P[i]!=P[j]) j=next[j];
next[i+1] = (P[i]==P[j])?j+1:0;
}
}
int main()
{
while(scanf("%s",P)==1)
{
vector<int> vc;
m = strlen(P);
getFail();
vc.push_back(m);
int i=m;
while(next[i])
{
i=next[i];
vc.push_back(i);
}
for(int i=vc.size()-1;i>0;i--)
printf("%d ",vc[i]);
printf("%d\n",vc[0]);
}
return 0;
}
几个字符使得最小循环节个数>=2
最小循环节长度为X=len-f[len] ,那么不符合时需要补充 X-LEN%X
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXM=100000+100;
int f[MAXM],m;
char P[MAXM];
void getFail(char *P,int *f)
{
f[0]=f[1]=0;
for(int i=1;i<m;i++)
{
int j=f[i];
while(j && P[i]!=P[j]) j=f[j];
f[i+1]= (P[i]==P[j])?j+1:0;
}
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%s",P);
m=strlen(P);
getFail(P,f);
if(f[m]==0)
{
printf("%d\n",m);
continue;
}
if(m%(m-f[m])==0) printf("0\n");
else
{
int len1=m-f[m];
printf("%d\n",len1-m%len1);
}
}
return 0;
}
重要结论:不论串S是不是循环的,如果想要S是一个循环串的话,那么它的最小循环节长度一定是len-f[len]. 其中len是串S的长度
给出串S,输出最小循环节可数
<span style="font-size:18px;">#include <iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
const int MAXN=1000000+100;
char P[MAXN];
int f[MAXN];
int m;
void getFail(char *P,int *f)
{
f[0]=f[1]=0;
for(int i=1;i<m;i++)
{
int j=f[i];
while(j && P[i]!=P[j]) j=f[j];
f[i+1]= (P[i]==P[j])?j+1:0;
}
}
int main()
{
while(scanf("%s",P)==1)
{
m=strlen(P);
if(m==1 && P[0]=='.')
break;
getFail(P,f);
if(m%(m-f[m])==0)printf("%d\n",m/(m-f[m]));//注意条件
else printf("1\n");
}
return 0;
}
</span>
给出串S,输出所有的循环节
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
const int MAXN=1000000+10;
int f[MAXN];
char P[MAXN];
int main()
{
int n,kase=1;
while(scanf("%d",&n)==1&&n)
{
scanf("%s",P);
f[0]=f[1]=0;
for(int i=1;i<n;i++)
{
int j=f[i];
while(j && P[i]!=P[j]) j=f[j];
f[i+1]= P[i]==P[j]?j+1:0;
}
printf("Test case #%d\n",kase++);
for(int i=2;i<=n;i++)
if(f[i]>0 && i%(i-f[i])==0) printf("%d %d\n",i,i/(i-f[i]) );
printf("\n");
}
return 0;
}
给出两个串S1 S2,求S1的最长前缀且是S2的最长后缀
用S1去匹配S2即可。
#include <iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
const int MAXN=50000+1000;
char T[MAXN],P[MAXN];
int f[MAXN],ex[MAXN];
int n,m;
void getFail()
{
f[0]=f[1]=0;
for(int i=1;i<m;i++)
{
int j=f[i];
while(j && P[i]!=P[j]) j=f[j];
f[i+1] = (P[i]==P[j])?j+1:0;
}
}
void find()
{
int j=0;
for(int i=0;i<n;i++)
{
while(j && T[i]!=P[j]) j=f[j];
if(T[i]==P[j])j++;
ex[i]=j;
}
}
int main()
{
while(scanf("%s %s",P,T)==2)
{
n=strlen(T);
m=strlen(P);
getFail();
find();
if(ex[n-1]==0)
printf("0\n");
else
{
for(int i=0;i<ex[n-1];i++)
printf("%c",P[i]);
printf(" %d\n",ex[n-1]);
}
}
return 0;
}
求多个串前后缀合并之后的最终串
就是利用答案串不断去匹配新的串,注意匹配开始的位置。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=1000000+66;
const ll mod=1e9+7;
int n;
string s[maxn];
string ans;
int nexts[maxn];
void getnexts(string t)
{
int len=t.size();
int i=0;
int j=-1;
nexts[0]=-1;
while(i<len)
{
if(j==-1||t[i]==t[j])
{
i++;
++j;
if(t[i]!=t[j])
nexts[i]=j;
else
nexts[i]=nexts[j];
}
else
{
j=nexts[j];
}
}
}
int kmp(string &s,string &t,int pos)//s是答案串
{
int i=pos;//
int j=0;
int sl=s.size();
int tl=t.size();
while(i<sl)
{
if(j==-1||s[i]==t[j])
{
++i;
++j;
}
else
{
j=nexts[j];
}
}
return j;
}
void add(string s)
{
getnexts(s);
int pos=kmp(ans,s,max(0,(int)ans.size()-(int)s.size()));
for(int i=pos; i<s.size(); i++)
{
ans.push_back(s[i]);
}
}
int main()
{
scanf("%d",&n);
for(int i=1; i<=n; i++)
{
cin>>s[i];
}
ans=s[1];
for(int i=2; i<=n; i++)
{
add(s[i]);
}
cout<<ans;
}
求一个串的后缀和另一个串的前缀合并之后的串且使得合并后的字符串尽量短,其次是使得合并后的字符串字典序尽量小
利用KMP,求出P1的前缀和P2的后缀匹配长度LEN1 和 P2的前缀和P1的后缀匹配长度LEN2。
若LEN1>LEN2那么把P1放前面,否则比较一下。
#include <iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
const int MAXN=100000+1000;
char *S,*T;
char str1[MAXN],str2[MAXN];
int next[MAXN];
int n,m;
void getFail(char *T)
{
next[0]=next[1]=0;
for(int i=1;i<m;i++)
{
int j=next[i];
while(j && T[i]!=T[j]) j=next[j];
next[i+1] = (T[i]==T[j])?j+1:0;
}
}
int KMP(char *S,char *T)
{
int j=0;
n=strlen(S);
m=strlen(T);
getFail(T);
for(int i=0;i<n;i++)
{
while(j && S[i]!=T[j]) j=next[j];
if(S[i]==T[j]) j++;
if(i==n-1) return j;
}
}
int main()
{
while(scanf("%s%s",str1,str2)==2)
{
int len1=KMP(str1,str2);
int len2=KMP(str2,str1);
if(len1==len2)
{
if(strcmp(str1,str2)<0)
{
printf("%s",str1);
printf("%s\n",str2+len1);
}
else
{
printf("%s",str2);
printf("%s\n",str1+len2);
}
}
else if(len1<len2)
{
printf("%s",str2);
printf("%s\n",str1+len2);
}
else
{
printf("%s",str1);
printf("%s\n",str2+len1);
}
}
return 0;
}
求一个串T在S串中出现次数
利用KMP,记录j==m(T串长度)有多少次。
#include <iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
char T[1000000+100],P[10000+100];
int f[10000+100];
int cnt;//统计出现的次数
void find(char *T,char *P,int *f)
{
int n=strlen(T);
int m=strlen(P);
int j=0;
for(int i=0;i<n;i++)
{
if(j && T[i]!=P[j]) j=f[j];
if(T[i]==P[j]) j++;
if(j==m) cnt++;
}
}
void getFail(char *P,int *f)
{
int m =strlen(P);
f[0]=f[1]=0;
for(int i=1;i<m;i++)
{
int j=f[i];
while(j && P[i]!=P[j]) j=f[j];
f[i+1] = P[i]==P[j]?j+1:0;
}
}
int main()
{
int K;
scanf("%d",&K);
while(K--)
{
cnt=0;
scanf("%s %s",P,T);
getFail(P,f);
find(T,P,f);
printf("%d\n",cnt);
}
return 0;
}