1 字符串hash
1.1 Codeforces 955D Scissors
我们设 (L_i) 表示在第二个字符串(以下称该字符串为 (t) )长度为 (i) 的前缀在第一个字符串(以下称该字符串为 (s) )出现最高左的位置是哪个位置。不难发现,去掉无意义的位置,(L) 这个数组一定是递增的,所以我们可以考虑用双指针来完成这个事情。
同样,我们可以预处理出 (R_i) 表示长度为 (i) 的后缀在 (s) 中出现最靠右的位置。
这样,(t) 被分成两半的情况就分别讨论完了,需要注意的是还有一种情况,就是你这个 (t) 可能存在于 (s) 非常靠左的位置或非常靠右的位置,且 (s) 的长度要小于 (t) ,在这种情况下,上面是讨论不到的。所以我们需要额外讨论一下 (t) 在 (s) 中出现的情况。这种情况我们直接取前缀的一段和后缀的一段就可以了。
代码:
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 500010
#define M number
using namespace std;
const int INF=0x3f3f3f3f;
const ull mod=13331;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
template<typename T> inline T Max(T a,T b){
return a<b?b:a;
}
template<typename T> inline T Min(T a,T b){
return a<b?a:b;
}
int n,m,L[N],R[N],k;
char s[N],t[N];
ull hs[N],ht[N],mpow[N];
inline ull GetHash(ull *h,int l,int r){
return h[r]-h[l-1]*mpow[r-l+1];
}
inline void prework(){
int l,r;l=1;r=1;
int minn=Min(m,k);
while(r-l+1<=minn&&r<=n-k){
ull ha=GetHash(hs,l,r);
ull hb=GetHash(ht,1,r-l+1);
if(ha==hb){
L[r-l+1]=r;
if(l>1) l--;
else r++;
}
else{l++;r++;}
}
for(int i=1;i<=m;i++) if(L[i]<k) L[i]=0;
l=r=k;
while(r-l+1<=minn&&r<=n-k&&L[r-l+1]==0){
ull ha=GetHash(hs,l,r);
ull hb=GetHash(ht,1,r-l+1);
if(ha==hb){
L[r-l+1]=r;
if(l>1) l--;
else r++;
}
else{l++;r++;}
}
l=r=n;
while(r-l+1<=minn&&l>=k+1){
ull ha=GetHash(hs,l,r);
ull hb=GetHash(ht,m-r+l,m);
if(ha==hb){
R[r-l+1]=l;
if(r<n) r++;
else l--;
}
else{l--;r--;}
}
for(int i=1;i<=m;i++) if(R[i]>n-k+1) R[i]=0;
l=r=n-k+1;
while(r-l+1<=minn&&l>=k+1&&R[r-l+1]==0){
ull ha=GetHash(hs,l,r);
ull hb=GetHash(ht,m-r+l,m);
if(ha==hb){
R[r-l+1]=l;
if(r<n) r++;
else l--;
}
else{l--;r--;}
}
for(int i=1;i<=m;i++){
if(!L[i]) L[i]=-INF;
if(!R[i]) R[i]=INF;
}
}
int main(){
// freopen("my.in","r",stdin);
// freopen("my.out","w",stdout);
read(n);read(m);read(k);
int maxx=Max(n,m);
mpow[0]=1;for(int i=1;i<=maxx;i++) mpow[i]=mpow[i-1]*mod;
scanf("%s%s",s+1,t+1);
for(int i=1;i<=n;i++) hs[i]=hs[i-1]*mod+s[i]-'a';
for(int i=1;i<=m;i++) ht[i]=ht[i-1]*mod+t[i]-'a';
prework();
// printf("L: ");for(int i=1;i<=m;i++) printf("%d ",L[i]);putchar('
');
// printf("R: ");for(int i=1;i<=m;i++) printf("%d ",R[i]);putchar('
');
for(int i=1;i<=m;i++){
if(L[i]==-INF||R[m-i]==INF) continue;
else if(L[i]<R[m-i]){
printf("Yes
");
printf("%d %d
",L[i]-k+1,R[m-i]);
return 0;
}
}
ull all=GetHash(ht,1,m);
for(int i=1;i<=n-m+1;i++){
ull now=GetHash(hs,i,i+m-1);
if(all==now){
printf("Yes
");
printf("1 %d
",n-k+1);
return 0;
}
}
printf("No
");
return 0;
}
/*
不能够从两边避免 k ,但是需要防止 k 的情况。
51 13 11
cbcbbcbbbbbcccbcccbbbcbbbbbbbbbbbbcbbbcbbcbbbbcbbbb
bbbbbccbcbbcb
ans:
Yes
3 38
*/
1.2 Codeforces 985F Isomorphic Strings
这个题我们只需要改变 hash 的方式,把每一个字符单独拿出来,用出现次数来做 hash,这样就可以完成这道题了。同时我们可以利用 multiset 来判断相等,这样可以避免 hash 出错。
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define int long long
#define uint unsigned int
#define ull unsigned long long
#define N 300000
#define M number
using namespace std;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
const int base=114514;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
int n,m,mpow[N],h[26][N];
char s[N];
inline int GetHash(int *h,int l,int r){
// printf("l:%d r:%d r-l+1:%d
",l,r,r-l+1);
return ((h[r]-h[l-1]*mpow[r-l+1]%mod)+mod)%mod;
}
signed main(){
// freopen("my.in","r",stdin);
// freopen("my.out","w",stdout);
read(n);read(m);scanf("%s",s+1);
mpow[0]=1;for(int i=1;i<N;i++) mpow[i]=(mpow[i-1]*base)%mod;
for(int i=1;i<=n;i++){
for(int j=0;j<=25;j++) h[j][i]=(h[j][i-1]*base+1+(s[i]-'a'==j))%mod;
}
for(int i=1;i<=m;i++){
int a,b,c;read(a);read(b);read(c);
multiset<int> s,t;
for(int j=0;j<=25;j++){
// printf("%lld %lld
",GetHash(h[j],a,a+c-1),GetHash(h[j],b,b+c-1));
s.insert(GetHash(h[j],a,a+c-1));
t.insert(GetHash(h[j],b,b+c-1));
}
printf("%s
",s==t?"YES":"NO");
}
}//
1.3 二维hash darkbzoj2351
二维 hash 其实和一维 hash 一样,也就是说我们拿第一个质数横着做一遍 hash,然后换一个质数,把 hash 值当成要做 hash 的值,竖着在做一遍 hash,这样就可以做了。
代码:
// #include<bits/stdc++.h>
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<cstdio>
#include<map>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 201
#define M 2010
using namespace std;
const int INF=0x3f3f3f3f;
const ull mod=131;
const ull base=13331;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
template<typename T> inline T Max(T a,T b){
return a<b?b:a;
}
ull mpow[M],bpow[M];
ull d[M][M],f[M][M];
// set<ull> S;
map<ull,int> S;
int m,n,a,b,q,ans[N*10];
char s[M];
int main(){
read(n);read(m);read(a);read(b);int maxx=Max(n,m);
mpow[0]=bpow[0]=1;
for(int i=1;i<=maxx;i++){
mpow[i]=mpow[i-1]*mod;bpow[i]=bpow[i-1]*base;
}
for(int i=1;i<=n;i++){
scanf("%s",s+1);
for(int j=1;j<=m;j++){
d[i][j]=d[i][j-1]*mod+s[j]-'0';
}
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
d[i][j]+=d[i-1][j]*base;
for(int i=1;i<=n-a+1;i++)
for(int j=1;j<=m-b+1;j++){
int x=i+a-1,y=j+b-1;
ull now=d[x][y]-d[x][j-1]*mpow[y-j+1];
ull now2=d[i-1][y]-d[i-1][j-1]*mpow[y-j+1];
ull nowans=now-now2*bpow[x-i+1];
// if(S.count(nowans)) ans[S[nowans]]=1;
S[nowans]=1;
}
read(q);
for(int i=1;i<=q;i++){
ull nowans=0;
for(int j=1;j<=a;j++){
ull now=0;
scanf("%s",s+1);
for(int k=1;k<=b;k++){
now*=mod;now+=s[k]-'0';
}
nowans*=base;nowans+=now;
}
// printf("i:%d nowans:%llu
",i,nowans);
// S[nowans]=i;
if(S.count(nowans)) ans[i]=1;
}
for(int i=1;i<=q;i++) printf("%d
",ans[i]);
}
1.4 UVA1401 Remember the Word
显然是一个 dp 。我们设 (f_i) 表示考虑完前 (i) 个字符的方案数,那么转移就是:
我们发现这个东西的复杂度在于判断是否在字典里,所以我们不妨反过来做:
设 (f_i) 表示考虑完 (s_{i,len}) 的方案数,转移和上面差不多,至于判断是否在字典里,我们可以建一棵 Trie 树来帮助我们判断,因为 Trie 树的节点深度不会超过 (100) ,所以复杂度为 (O(100n))。
代码:
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 400010
#define M 110
using namespace std;
const int INF=0x3f3f3f3f;
const int mod=20071027;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
int ch[N][26],tot,End[N],n,f[N];
char s[N],t[M];
inline void Insert(char *s){
int len=strlen(s+1),p=0;
for(int i=1;i<=len;i++){
int c=s[i]-'a';
if(!ch[p][c]) ch[p][c]=++tot;
p=ch[p][c];
}
End[p]=1;
}
int main(){
// freopen("my.in","r",stdin);
// freopen("my.out","w",stdout);
int test=0;
while(scanf("%s",s+1)!=EOF){
test++;
memset(ch,0,sizeof(ch));
memset(End,0,sizeof(End));
read(n);
for(int i=1;i<=n;i++){
scanf("%s",t+1);Insert(t);
}
memset(f,0,sizeof(f));
int len=strlen(s+1);f[len+1]=1;
for(int i=len;i>=1;i--){
int p=0;
for(int j=i;j<=len;j++){
int c=s[j]-'a';
if(!ch[p][c]) break;
p=ch[p][c];if(End[p]) (f[i]+=f[j+1])%=mod;
}
}
printf("Case %d: ",test);
printf("%d
",f[1]);
}
return 0;
}
1.5 UVA1519 Dictionary Size
看到前缀和后缀我们首先想到 Trie 树。我们正向建一棵 Trie 树,反向建一棵 Trie 树。那么答案我们首先认为是两颗 Trie 树的节点个数减 (1) 的乘积。
不难发现,有一些字符串我们没有统计,就是那些长度为 (1) 的字符串,这是因为我们上面的操作只统计了长度大于等于 (2) 的字符串。与此同时,我们有一些字符串被重复统计,不难发现,重复统计的次数是每一个字符在 Trie 树上的出现次数之积,减去就可以了。
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define int long long
#define ull unsigned long long
#define N 400010
#define M 50
using namespace std;
const int INF=0x3f3f3f3f;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
char s[M];
int n,ans,c[M];
struct Trie{
int p[N][26],tot;
int cnt[M];
inline Trie(){tot=0;}
inline void Insert(char *s){
int now=0;int len=strlen(s);
for(int i=0;i<len;i++){
int k=s[i]-'a';
if(!p[now][k]){
p[now][k]=++tot;
if(i){
cnt[k]++;
}
}
now=p[now][k];
}
}
inline void clear(){
tot=0;memset(p,0,sizeof(p));
memset(cnt,0,sizeof(cnt));
}
};
Trie t1,t2;
signed main(){
while(cin>>n){
for (int i = 0; i < n; i++) {
scanf("%s", s);
int n = strlen(s);
if (n == 1)
c[s[0] - 'a'] = 1;
t1.Insert(s);
reverse(s, s + n);
t2.Insert(s);
}
ans=t1.tot*t2.tot;
for(int i=0;i<=25;i++){
if(c[i]) ans++;
ans-=(t1.cnt[i]*t2.cnt[i]);
}
printf("%lld
",ans);
t1.clear();t2.clear();
ans=0;memset(c,0,sizeof(c));
}
}
2 AC自动机
2.1 P4052 [JSOI2007]文本生成器
首先关注到不出现,不出现的话考虑用 AC 自动机,主要是借助 Trie 树这个结构。我们简单的取一下补集。
既然不能出现,那么也就是说不能做到结束节点,更进一步,我们在 Trie 树上走,我们也不能走到一个节点,其所表示的字符串的后缀是这 (n) 个字符串中的一个。换句话说,我们要把所有满足后缀不合法的节点标记一下,怎么标记?我们在建立 AC 自动机的时候把这个节点的 end
与其后缀链接的 end
或一下就可以了。
然后就比较套路了,我们设 (f_{i,j}) 表示走了 (i) 步,在 Trie 图上的 (j) 节点,方案数。转移就枚举下一步要去哪里即可。然后把每个位置上走了 (m) 步的方案数累加就可以了。
值得一提的是,Trie 图并不是 DAG ,不用担心在 Trie 图上做 dp 的正确性。
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 110
#define M 6100
using namespace std;
const int INF=0x3f3f3f3f;
const int mod=1e4+7;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
struct node{
int ch[26],end,fail;
};
int f[N][M],n,m;
char s[N];
struct AC{
node p[M];int tot;
inline AC(){memset(p,0,sizeof(p));tot=0;}
inline void Insert(char *s){
int len=strlen(s),now=0;
for(int i=0;i<=len-1;i++){
int k=s[i]-'A';
if(!p[now].ch[k]) p[now].ch[k]=++tot;
now=p[now].ch[k];
}
p[now].end=1;
}
inline void GetFail(){
queue<int> q;
while(q.size()) q.pop();
for(int i=0;i<=25;i++) if(p[0].ch[i]) q.push(p[0].ch[i]);
while(q.size()){
int top=q.front();q.pop();
for(int i=0;i<=25;i++){
if(p[top].ch[i]){
p[p[top].ch[i]].fail=p[p[top].fail].ch[i];
p[p[top].ch[i]].end|=p[p[p[top].fail].ch[i]].end;
q.push(p[top].ch[i]);
}
else p[top].ch[i]=p[p[top].fail].ch[i];
}
}
}
inline void DP(){
f[0][0]=1;
for(int i=1;i<=m;i++){
for(int j=0;j<=tot;j++){
for(int k=0;k<=25;k++){
if(p[p[j].ch[k]].end==1) continue;
(f[i][p[j].ch[k]]+=f[i-1][j])%=mod;
}
}
}
}
inline int GetAns(){
int ans=0;
for(int i=0;i<=tot;i++){
ans+=f[m][i];ans%=mod;
}
return ans;
}
};
AC ac;
inline int ksm(int a,int b,int mod){
int res=1;
while(b){
if(b&1) (res*=a)%=mod;
a=a*a%mod;
b>>=1;
}
return res;
}
int main(){
// freopen("my.in","r",stdin);
// freopen("my.out","w",stdout);
ios::sync_with_stdio(false);cin.tie(0);
// read(n);read(m);
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>s;
ac.Insert(s);
}
ac.GetFail();ac.DP();
int ans=ac.GetAns();
// printf("%d
",((ksm(26,m,mod)-ans)%mod+mod)%mod);
cout<<((ksm(26,m,mod)-ans)%mod+mod)%mod<<"
";
return 0;
}
/*
update 1:
row 48 "p[p[top].ch[i]].end|=p[p[top].fail].ch[i];"
update 2:
row 101 -> row 100
update 3
row 93 -> 92
*/
2.2 P4600 [HEOI2012]旅行问题
建立 AC 自动机,在 fail 树上求 LCA 就可以了。
代码:
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 3000010
#define M number
using namespace std;
const int INF=0x3f3f3f3f;
const ll mod=1e9+7;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
vector<int> ID[N];
struct node{
int ch[26],fail,end;
ll code;
};
struct edge{
int to,next;
inline void intt(int to_,int ne_){
to=to_;next=ne_;
}
};
edge li[N];
int head[N],tail;
inline void add(int from,int to){
li[++tail].intt(to,head[from]);
head[from]=tail;
}
struct AC{
node p[N];int size;
inline AC(){size=1;}
inline void Insert(int id,char *s){
int len=strlen(s);int now=1;
for(int i=0;i<=len-1;i++){
int k=s[i]-'a';
if(!p[now].ch[k]) p[now].ch[k]=++size;
int last=now;now=p[now].ch[k];
p[now].code=(p[last].code*26%mod+k)%mod;
ID[id].push_back(now);
}
p[now].end++;
}
inline void GetFail(){
queue<int> q;
for(int i=0;i<=25;i++){
if(p[1].ch[i]){q.push(p[1].ch[i]);p[p[1].ch[i]].fail=1;}
else p[1].ch[i]=1;
}
while(q.size()){
int top=q.front();q.pop();
for(int i=0;i<=25;i++){
if(p[top].ch[i]){
p[p[top].ch[i]].fail=p[p[top].fail].ch[i];
q.push(p[top].ch[i]);
}
else p[top].ch[i]=p[p[top].fail].ch[i];
}
}
}
inline void BuildFailTree(){
for(int i=2;i<=size;i++){
// printf("i:%d fail:%d
",i,p[i].fail);
add(p[i].fail,i);
}
}
};
AC ac;
int n,q;
char s[N];
int top[N],son[N],siz[N],deep[N],fa[N];
inline void dfs1(int k,int fat){
deep[k]=deep[fat]+1;siz[k]=1;fa[k]=fat;
for(int x=head[k];x;x=li[x].next){
int to=li[x].to;
if(to==fat) continue;
dfs1(to,k);
siz[k]+=siz[to];
if(siz[son[k]]<siz[to]) son[k]=to;
}
}
inline void dfs2(int k,int t){
top[k]=t;
if(son[k]) dfs2(son[k],t);
for(int x=head[k];x;x=li[x].next){
int to=li[x].to;
if(to==fa[k]||to==son[k]) continue;
dfs2(to,to);
}
}
inline int GetLca(int a,int b){
while(top[a]!=top[b]){
if(deep[top[a]]<deep[top[b]]) swap(a,b);
a=fa[top[a]];
}
if(deep[a]>deep[b]) swap(a,b);
return a;
}
int main(){
// freopen("my.in","r",stdin);
// freopen("my.out","w",stdout);
read(n);
for(int i=1;i<=n;i++){
scanf("%s",s);ac.Insert(i,s);
}
ac.GetFail();ac.BuildFailTree();
dfs1(1,0);dfs2(1,1);
read(q);
for(int i=1;i<=q;i++){
int a,b,c,d;read(a);read(b);read(c);read(d);
int p1=ID[a][b-1],p2=ID[c][d-1];
// printf("p1:%d p2:%d
",p1,p2);
// printf("lca:%d
",GetLca(p1,p2));
printf("%lld
",ac.p[GetLca(p1,p2)].code);
}
return 0;
}
3 KMP 算法
KMP 算法不会考匹配,而会考对 next 数组的理解和运用。
3.1 HDU3336
令 (f_i) 表示以 (i) 结尾的所有的前缀的出现次数,那么显而易见的转移是 (f_i=f_{next_i}+1) ,之所以加 (1) 是整个长度为 (i) 的字符串,所有以 (next_i) 结尾的前缀都会在以 (i) 结尾中出现 (1) 次。
代码:
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 200010
#define M number
using namespace std;
const int INF=0x3f3f3f3f;
const int mod=10007;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
int t,n,nxt[N],f[N],ans;
char s[N];
int main(){
read(t);
while(t--){
read(n);
scanf("%s",s+1);
nxt[1]=0;
for(int i=2,j=0;i<=n;i++){
while(j>0&&s[j+1]!=s[i]) j=nxt[j];
if(s[j+1]==s[i]) j++;
nxt[i]=j;
}
for(int i=1;i<=n;i++){
f[i]=f[nxt[i]]+1;f[i]%=mod;
ans+=f[i];ans%=mod;
}
printf("%d
",ans);
for(int i=1;i<=n;i++) nxt[i]=f[i]=0;ans=0;
}
}
3.2 POJ 2752
KMP 的题我用字符串 hash 做的,就这样吧。。。
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 400010
#define M number
using namespace std;
const int INF=0x3f3f3f3f;
const ull mod=13331;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
ull a,b;
char s[N];
int main(){
// freopen("my.in","r",stdin);
// freopen("my.out","w",stdout);
while(cin>>(s+1)){
a=b=0;
ull ji=1;int n=strlen(s+1);
for(int i=1;i<=n;i++){
a*=mod;a+=s[i]-'a';
int j=n-i+1;int siz=n-j;
b+=ji*(s[j]-'a');ji*=mod;
if(a==b) printf("%d ",i);
}
putchar('
');
}
}