# 7.21 學習筆記
1 字串hash
1.1 Codeforces 955D Scissors
我們設 \(L_i\) 表示在第二個字串(以下稱該字串為 \(t\) )長度為 \(i\) 的字首在第一個字串(以下稱該字串為 \(s\) )出現最高左的位置是哪個位置。不難發現,去掉無意義的位置,\(L\) 這個陣列一定是遞增的,所以我們可以考慮用雙指標來完成這個事情。
同樣,我們可以預處理出 \(R_i\) 表示長度為 \(i\) 的字尾在 \(s\) 中出現最靠右的位置。
這樣,\(t\) 被分成兩半的情況就分別討論完了,需要注意的是還有一種情況,就是你這個 \(t\) 可能存在於 \(s\) 非常靠左的位置或非常靠右的位置,且 \(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('\n'); // printf("R: ");for(int i=1;i<=m;i++) printf("%d ",R[i]);putchar('\n'); 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\n"); printf("%d %d\n",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\n"); printf("1 %d\n",n-k+1); return 0; } } printf("No\n"); 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\n",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\n",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\n",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\n",i,nowans);
// S[nowans]=i;
if(S.count(nowans)) ans[i]=1;
}
for(int i=1;i<=q;i++) printf("%d\n",ans[i]);
}
1.4 UVA1401 Remember the Word
顯然是一個 dp 。我們設 \(f_i\) 表示考慮完前 \(i\) 個字元的方案數,那麼轉移就是:
\[f_i=\sum\limits_{s_{i+1,j}\in dictionary}f_j+1 \]我們發現這個東西的複雜度在於判斷是否在字典裡,所以我們不妨反過來做:
設 \(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\n",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\n",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\n",((ksm(26,m,mod)-ans)%mod+mod)%mod);
cout<<((ksm(26,m,mod)-ans)%mod+mod)%mod<<"\n";
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\n",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\n",p1,p2);
// printf("lca:%d\n",GetLca(p1,p2));
printf("%lld\n",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\n",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('\n');
}
}