重修字符串
之前学得太屑了,现在重修一下。
一.普通字典树(trie):
1.效用:
维护一个字符串的集合,能够高效地插入新的字符串到集合中,也能高效地查找某个字符串或某个前缀是否在集合中。
2.实现:
(1):如何插入一个新的字符串,insert。
从根节点一直往下匹配,若无此字母,则新开一个关于此字母的节点,若匹配完成,则给当前节点标记末尾。
inline void ins(string s) { int x=0,len=s.size(); for(register int i=0;i<len;i++) { int c=s[i]-'a'; if(!ch[x][c]) { cnt++; ch[x][c]=cnt; } x=ch[x][c]; } end[x]=true; return; }
(2):如何查询一个字符串是否在字典树中,query(ask)。
直接扫一遍,查询是否有,若无直接返回不。
inline bool ask(string s) { int x=0,len=s.size(); for(register int i=0;i<len;i++) { int c=s[i]-'a'; if(ch[x][c]==0) return false; x=ch[x][c]; } return end[x]; }
典型例题:
例一 P2580 于是他错误的点名开始了
经典的 树板子,就是要注意一下在哪里记录。
Code
点击查看代码
#include<bits/stdc++.h> using namespace std; const int MAXN=1e6+5; inline int read() { register int x=0,f=1; register char ch=getchar(); while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); } while(isdigit(ch)) { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x*f; } int ch[MAXN][27],cnt; int en[MAXN]; int vis[MAXN]; inline void ins(string s) { int x=0; int len=s.size(); for(register int i=0;i<len;i++) { int c=s[i]-'a'; if(!ch[x][c]) { cnt++; ch[x][c]=cnt; } x=ch[x][c]; } en[x]=1; return; } inline int ask(string s) { int x=0,len=s.size(); for(register int i=0;i<len;i++) { int c=s[i]-'a'; if(!ch[x][c]) return 3; x=ch[x][c]; } if(!en[x]) return 3; if(!vis[x]) { vis[x]++; return 1; } return 2; } int n,m; string s; int main() { ios_base::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>n; for(register int i=1;i<=n;i++) cin>>s,ins(s); cin>>m; for(register int i=1;i<=m;i++) { cin>>s; int ans=ask(s); if(ans==1) { printf("OK\n"); continue; } else if(ans==2) { printf("REPEAT\n"); continue; } else if(ans==3) { printf("WRONG\n"); continue; } } return 0; }
例二 SP4033 PHONELST-Phone List
倍经验:UVA11362 Phone List(一毛一样)
倍经验:UVA644 Immediate Decodability(基本一样)
若当前插入的是 :
情况一:别的串是 的前缀,那么 到遍历结束之前,一定至少有一个染色的点。
情况二: 是别的串的前缀,那么 的插入过程不会有任何新的节点产生,只需要判断 是否没变。
Code
点击查看代码
#include<bits/stdc++.h> using namespace std; const int MAXN=1e6+5; inline int read() { register int x=0,f=1; register char ch=getchar(); while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); } while(isdigit(ch)) { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x*f; } int ch[MAXN][27],cnt; int en[MAXN]; inline bool solve(string s) { int x=0,len=s.size(); bool flag=true; for(register int i=0;i<len;i++) { int c=s[i]-'0'; if(!ch[x][c]) { cnt++; ch[x][c]=cnt; flag=false; } x=ch[x][c]; if(en[x]) return true; } en[x]=true; if(flag) return true; return false; } int t,n; string s; bool vis; int main() { ios_base::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>t; while(t--) { memset(ch,0,sizeof(ch)); memset(en,0,sizeof(en)); vis=false; cnt=0; cin>>n; for(register int i=1;i<=n;i++) { cin>>s; if(solve(s)) vis=true; } if(vis) { printf("NO\n"); continue; } else { printf("YES\n"); continue; } } return 0; }
例三 P3879 阅读理解
Code
点击查看代码
#include<bits/stdc++.h> using namespace std; const int MAXN=5e5+10; const int N=1005; int ch[MAXN][27],cnt; string s; bitset<N>ed[MAXN]; int t,n,m; inline void insert(int x,string s) { int x=0; int len=s.size(); for(register int i=0;i<len;i++) { int c=s[i]-'a'; if(!trie[x][c]) trie[x][c]=++cnt; x=trie[x][c]; } ed[x][c]=true; } inline void ask(string s) { int x=0; bool flag=1; for(register int i=0;i<len;i++) { int c=s[i]-'a'; if(ch[x][c]==0) { flag=0; break; } x=ch[x][c]; } if(flag) for(register int i=1;i<=n;i++) if(ed[x][i]) printf("%d ",i); puts(""); } int main() { ios_base::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>n; for(register int i=1;i<=n;i++) { cin>>t; for(register int j=1;j<=t;j++) { cin>>s; insert(i,s); } } cin>>m; for(register int i=1;i<=m;i++) { cin>>s; ask(s); } return 0; }
例四 P2922 Secret Message G
讲实话,题面有点糊。
我们来分析一下:
1. 条信息, 条暗号。
2. 条信息是 条暗号的前缀,或 条暗号是 条信息的前缀。
那么做法就很显然了。
Code
点击查看代码
#include<bits/stdc++.h> using namespace std; const int MAXN=5e5+5; const int N=1e4+5; int m,n,ch[MAXN][2],cnt; int k,sum[MAXN],ed[MAXN]; inline void ins(int t,int p[]) { int x=0; for(register int i=1;i<=t;i++) { if(!ch[x][p[i]]) ch[x][p[i]]=++cnt; x=ch[x][p[i]]; sum[x]++; } ed[x]++; return; } inline int ask(int t,int p[]) { int x=0,ans=0; for(register int i=1;i<=t;i++) { if(ch[x][p[i]]==0) return ans; x=ch[x][p[i]]; ans+=ed[x]; } return ans-ed[x]+sum[x]; } int p[MAXN]; int main() { ios_base::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>m>>n; for(register int i=1;i<=m;i++) { cin>>k; for(register int i=1;i<=k;i++) cin>>p[i]; ins(k,p); } for(register int i=1;i<=n;i++) { cin>>k; for(register int i=1;i<=k;i++) cin>>p[i]; printf("%d\n",ask(k,p)); } return 0; }
例五 P2292 L语言
这题卡空间真恶心
这道题虽然正解是 AC 自动机,题解区都是 AC 自动机的解法,但作为一个专业整活人,我决定来尝试一下 树的写法。
首先我们可以想到在 树上做 ,但很显然 这样的复杂度是 ,前 能轻松过,但更新的数据会被卡掉。
Code
点击查看代码
#include<bits/stdc++.h> using namespace std; const int N=1e6+5; const int M=26; bool en[N],vis[N<<1]; int ch[N][M],cnt,ans; string ss; inline void insert(string s) { int x=0,len=s.size(); for(register int i=0;i<len;i++) { int c=s[i]-'a'; if(ch[x][c]==0) ch[x][c]=++cnt; x=ch[x][c]; } en[x]=true; return; } inline void dfs(int pos, int cur) { int c=ss[pos]-'a'; cur=ch[cur][c]; if(cur) dfs(pos+1,cur); if(en[cur]==true) { ans=max(pos+1,ans); if(vis[pos]==false) dfs(pos+1,0); vis[pos]=true; } return; } int n,m; int main() { ios_base::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>n>>m; for(register int i=1;i<=n;i++) { string word; cin>>word; insert(word); } for(register int i=1;i<=m;i++) { memset(vis,false,sizeof(vis)); ans=0; cin>>ss; dfs(0,0); cout<<ans<<endl; } return 0; }
二.01字典树(01trie):
1.定义:
一颗只含 串的字典树,用于插入一些转化成二进制的整数。
2.实现:
insert函数:
inline void ins(int pos) { int x=0; for(register int i=31;i>=0;i--) { int c=(pos>>i)&1; if(!ch[x][c]) ch[x][c]=++cnt; x=ch[x][c]; } return; }
ask函数:
inline int ask(int pos) { int ans=0; int x=0; for(register int i=31;i>=0;i--) { int c=(pos>>i)&1; if(ch[x][c^1]) { ans+=(c^1)<<i; x=ch[x][c^1]; } else { ans+=c<<i; x=ch[x][c]; } } return ans; }
典型例题:
例一 HDU4825 Xor Sum
思路:
1.将 个正整数转化成二进制并插入 树;
2.询问时将一个整数 转化成二进制从根节点(高位)开始搜索,尽量取与 位上数值相反的数。
Code
点击查看代码
#include<bits/stdc++.h> using namespace std; const int MAXN=4e6+5; int ch[MAXN][2]; int cnt; inline void ins(int pos) { int x=0; for(register int i=31;i>=0;i--) { int c=(pos>>i)&1; if(!ch[x][c]) ch[x][c]=++cnt; x=ch[x][c]; } return; } inline int ask(int pos) { int ans=0; int x=0; for(register int i=31;i>=0;i--) { int c=(pos>>i)&1; if(ch[x][c^1]) { ans+=(c^1)<<i; x=ch[x][c^1]; } else { ans+=c<<i; x=ch[x][c]; } } return ans; } inline void init() { memset(ch,0,sizeof(ch)); cnt=0; return; } int t; int n,m; int main() { ios_base::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>t; for(register int i=1;i<=t;i++) { cin>>n>>m; init(); for(register int j=1;j<=n;j++) { int x; cin>>x; ins(x); } printf("Case #%d:\n",i); for(register int j=1;j<=m;j++) { int x; cin>>x; printf("%d\n",ask(x)); } } return 0; }
例二 HDU5536 Chip Factory
Code
点击查看代码
#include<bits/stdc++.h> #define int long long using namespace std; const int MAXN=1e3+5; int a[MAXN]; int ch[MAXN*35][2],cnt; int num[MAXN*35]; inline void ins(int pos,int val) { int x=0; for(register int i=31;i>=0;i--) { int c=(pos>>i)&1; if(!ch[x][c]) ch[x][c]=++cnt; x=ch[x][c]; num[x]+=val; } return; } inline int ask(int pos) { int x=0,ans=0; for(register int i=31;i>=0;i--) { int c=(pos>>i)&1; if(ch[x][c^1] && num[ch[x][c^1]]) { ans|=1<<i; x=ch[x][c^1]; } else x=ch[x][c]; } return ans; } inline void init() { memset(ch,0,sizeof(ch)); memset(num,0,sizeof(num)); cnt=0; return; } int t; int n; int ans; signed main() { ios_base::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>t; while(t--) { cin>>n; ans=-1; init(); for(register int i=1;i<=n;i++) cin>>a[i]; for(register int i=1;i<=n;i++) ins(a[i],1); for(register int i=1;i<=n;i++) { ins(a[i],-1); for(register int j=i+1;j<=n;j++) { ins(a[j],-1); ans=max(ans,ask(a[i]+a[j])); ins(a[j],1); } ins(a[i],1); } printf("%lld\n",ans); } return 0; }
例三 P4551 最长异或路径
思路:
1.定义 表示从根节点到点 的路径上的边权异或和。
2. 维护每个点 的 。
3.将 依次询问最大的异或值,再插入 树。
Code
点击查看代码
#include<bits/stdc++.h> using namespace std; const int MAXN=1e5+5; struct edge { int to,nxt,len; }e[MAXN<<1]; int head[MAXN],cnt; inline void add(int x,int y,int z) { e[++cnt].to=y; e[cnt].len=z; e[cnt].nxt=head[x]; head[x]=cnt; return; } int n,tot,ch[MAXN*32][2]; int sum[MAXN]; inline void dfs(int x,int fa) { for(register int i=head[x];i;i=e[i].nxt) { int y=e[i].to,z=e[i].len; if(y==fa) continue; sum[y]^=sum[x]; sum[y]^=z; dfs(y,x); } return; } inline void ins(int pos) { int x=0; for(register int i=31;i>=0;i--) { int c=(pos>>i)&1; if(!ch[x][c]) ch[x][c]=++tot; x=ch[x][c]; } return; } inline int ask(int pos) { int ans=0; int x=0; for(register int i=31;i>=0;i--) { int c=(pos>>i)&1; if(ch[x][c^1]) { ans+=1<<i; x=ch[x][c^1]; } else x=ch[x][c]; } return ans; } int main() { ios_base::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>n; for(register int i=1;i<=n-1;i++) { int x,y,z; cin>>x>>y>>z; add(x,y,z); add(y,x,z); } dfs(1,0); for(register int i=1;i<=n;i++) ins(sum[i]); int ans=0; for(register int i=1;i<=n;i++) ans=max(ans,ask(sum[i])); printf("%d\n",ans); return 0; }
例四 CF842D Vitya and Strange Lesson
考虑常用的操作异或集合的手段,我的第一反应是线性基,然而线性基只能插入,不能修改,再者她也不能查询 这么奇怪的东西。
先不考虑异或,显然 是有可二分性的,我们需要一个数据结构支持查询小于某个数的数是否都出现过,然后我们就可以二分了,我的第一反应是值域线段树,显然是可以做的,直接在线段树上二分就可以了,具体来说走到线段树的某个节点时,看一下左儿子存在的数是不是等于左儿子对应的值域大小,即看一下左儿子是不是满的。然而,线段树也不能维护异或这么奇怪的东西。
实际上我们可以选取和线段树结构相同的另一个数据结构(或者说她们就是同一个东西), 。
我们也可以在 上二分查 ,并且走到 上某个点时,如果异或的数这一位为 ,意义就是交换 的左右儿子,然后就可以用和线段树一样的方法做了。
有一个细节就是,在计算 一个点的 时,注意一个数出现多次只计算一次。
Code
点击查看代码
#include<bits/stdc++.h> using namespace std; const int MAXN=3e5+5; int ch[MAXN*32][2],cnt; int siz[MAXN*32]; int tag; int n,m; inline void ins(int pos) { int x=0; for(register int i=31;i>=0;i--) { int c=(pos>>i)&1; if(!ch[x][c]) ch[x][c]=++cnt; x=ch[x][c]; } return; } inline int ask(int pos) { int x=0; int ans=0; for(register int i=31;i>=0;i--) { int c=(pos>>i)&1; if(siz[ch[x][c]]==(1<<(i+1))-1) { ans+=1<<i; x=ch[x][c^1]; } else x=ch[x][c]; } return ans; } inline void dfs(int x) { siz[x]=1; for(register int i=0;i<=1;i++) { int y=ch[x][i]; if(!y) continue; dfs(y); siz[x]+=siz[y]; } return; } int main() { ios_base::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>n>>m; for(register int i=1;i<=n;i++) { int x; cin>>x; ins(x); } dfs(0); for(register int i=1;i<=m;i++) { int x; cin>>x; tag^=x; printf("%d\n",ask(tag)); } return 0; }
例五 CF817E Choosing The Commander
思路:
明显是一个 处理插入删除操作的题目,只是多了一个查询操作。
对于插入删除操作,直接弄一个 数组来记录插入和删除操作,然后套板子即可,不再赘述。
然后考虑询问。
我们可以做出这样的操作,对于当前讨论的这一位:
- 若 ,则统计 子树上的答案,并向 的子树继续遍历,因为此时可以保证整个左子树都小于 。
- 若 ,则向 的子树继续遍历。
然后这题还要注意的一点是,它的异或 并不是累积的,所以每次要清空。
Code
点击查看代码
#include<bits/stdc++.h> using namespace std; const int MAXN=3e6+5; int ch[MAXN][2],cnt; int num[MAXN]; int n; inline void ins(int pos,int val) { int x=0; for(register int i=31;i>=0;i--) { int c=(pos>>i)&1; if(!ch[x][c]) ch[x][c]=++cnt; x=ch[x][c]; num[x]+=val; } return; } inline int ask(int pos,int tag) { int x=0; int ans=0; for(register int i=31;i>=0;i--) { int t=(pos>>i)&1,c=(tag>>i)&1; if(t==1) { ans+=num[ch[x][c]]; x=ch[x][c^1]; } else x=ch[x][c]; if(x==0) return ans; } return ans; } int main() { ios_base::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>n; for(register int i=1;i<=n;i++) { int op,x; cin>>op>>x; if(op==1) ins(x,1); if(op==2) ins(x,-1); if(op==3) { int y; int tag=0; cin>>y; tag^=x; printf("%d\n",ask(y,tag)); } } return 0; }
三.可持久化01 trie:
可持久化01 trie模板:
#include<bits/stdc++.h> using namespace std; const int MAXN=1e7+5; int cnt[MAXN]; int ch[MAXN][2],s[MAXN]; inline void ins(int now,int ls,int x,int p)//now为当前版本节点,ls为上个版本对应节点(rt[i]与rt[i-1]) { if(p<0) return; int t=(x>>p)&1;//取出第p位 ch[now][!t]=ch[ls][!t];//与上一个版本相连 ch[now][t]=++cnt;//给新节点 s[ch[now][t]]=s[ch[ls][t]]+1;//记录在前缀中出现次数 ins(ch[now][t],ch[ls][t],x,p-1); return; } inline int ask(int l,int r,int x,int p)//l为左边界,r为右边界 { if(p<0) return 0; int t=(x>>p)&1; if(s[ch[r][!t]]>s[ch[l][!t]]) return (1<<p)+ask(ch[l][!t],ch[r][!t],x,p-1); else return ask(ch[l][t],ch[r][t],x,p-1); }
典型例题
例一 P4735 最大异或和
点击查看代码
#include<bits/stdc++.h> using namespace std; const int MAXN=2e7+5; int n,m; int cnt; int ch[MAXN][2],s[MAXN]; int rt[MAXN]; int sum[MAXN]; inline void insert(int now,int ls,int x,int p) { if(p<0) return; int t=(x>>p)&1; ch[now][!t]=ch[ls][!t]; ch[now][t]=++cnt; s[ch[now][t]]=s[ch[ls][t]]+1; insert(ch[now][t],ch[ls][t],x,p-1); return; } inline int ask(int l,int r,int x,int p) { if(p<0) return 0; int t=(x>>p)&1; if(s[ch[r][!t]]>s[ch[l][!t]]) return (1<<p)+ask(ch[l][!t],ch[r][!t],x,p-1); else return ask(ch[l][t],ch[r][t],x,p-1); } int main() { scanf("%d%d",&n,&m); rt[0]=++cnt; insert(rt[0],0,0,23); for(register int i=1;i<=n;i++) { int op; scanf("%d",&op); sum[i]=sum[i-1]^op; rt[i]=++cnt; insert(rt[i],rt[i-1],sum[i],23); } for(register int i=1;i<=m;i++) { char op; int l,r,k; cin>>op; if(op=='A') { scanf("%d",&k); n++; sum[n]=sum[n-1]^k; rt[n]=++cnt; insert(rt[n],rt[n-1],sum[n],23); } else { scanf("%d%d%d",&l,&r,&k); l--,r--; printf("%d\n",ask(rt[l-1],rt[r],k^sum[n],23)); } } }
例二 P5283 异或粽子
给你个数字,然后让你给出各不同的区间,然求区间最大异或和。
我们想,,
所以可以将区间的异或转为两个值异或。
可以拿来乱搞一下。
因为其实从高位置低位,若让其尽可能大,则要让两个异或值尽可能大。
点击查看代码
#include<bits/stdc++.h> #define int long long using namespace std; const int MAXN=1e6+5; int n,k,rt[MAXN],cnt; int a[MAXN],ans; struct node { int ch[2],siz,id; }t[MAXN*40]; inline void insert(int &now,int pre,int bit,int id,int val) { now=++cnt; t[now]=t[pre];t[now].siz++; if(bit==-1){t[now].id=id;return;} if((val>>bit)&1) insert(t[now].ch[1],t[pre].ch[1],bit-1,id,val); else insert(t[now].ch[0],t[pre].ch[0],bit-1,id,val); return; } inline int ask(int u,int v,int bit,int val) { if(bit==-1) return t[v].id; int d=(val>>bit)&1; if(t[t[v].ch[d^1]].siz-t[t[u].ch[d^1]].siz>0) return ask(t[u].ch[d^1],t[v].ch[d^1],bit-1,val); return ask(t[u].ch[d],t[v].ch[d],bit-1,val); } struct Node { int l,r,x,id,val; Node(int ll=0,int rr=0,int xx=0) { l=ll; r=rr; x=xx; id=ask(rt[l-1],rt[r],31,a[x]); val=a[x]^a[id-1]; } }; inline bool operator<(const Node &a,const Node &b) { return a.val<b.val; } priority_queue<Node>pq; signed main() { scanf("%lld%lld",&n,&k); for(register int i=1;i<=n;i++) { int op; scanf("%lld",&op); a[i]=a[i-1]^op; } for(register int i=1;i<=n;i++) { rt[i]=rt[i-1]; insert(rt[i],rt[i],31,i,a[i-1]); } for(register int i=1;i<=n;i++) pq.push(Node(1,i,i)); while(k--) { Node u=pq.top(); pq.pop(); ans+=u.val; if(u.l<u.id) pq.push(Node(u.l,u.id-1,u.x)); if(u.id<u.r) pq.push(Node(u.id+1,u.r,u.x)); } printf("%lld\n",ans); return 0; }
三.KMP算法:
1.解决的问题:
在一个字典串中查询一个查询串的位置。
2.实现:
我们可以直接暴力匹配,但显然,时间复杂度会炸,于是就考虑 。
我们用如下方法匹配:
我们找到一个前缀与一个后缀,即图中的阴影部分。
且我们的工作原理就是 指针不动, 指针向前跳,这样才能实现将查询串向后拖的效果。
关键点:
- 一旦字符串失配,我们希望查询串能尽可能多的跳跃,而不是逐位移动;
- 能够跳跃的最大距离由查询串的最长公共前后缀的长度决定。
对于上面的例子:
- 失配前字串
- 前缀集合
- 后缀集合
- 最长公共前后缀
然后我们可以用个 表来理解一下:
那么如何求 数组呢?
- 求 数组也是字符串匹配的过程;
- 将目标串查询串当作主串,并把查询串自己当作目标串,自己匹配自己;
- 应该从 开始。
下图展示了其工作过程:
函数:
int nxt[MAXN]; int len1,len2; inline void getnxt(string s) { nxt[0]=-1; int i=0,j=-1; while(i<len2) { if(j==-1 || s2[i]==s2[j]) i++,j++,nxt[i]=j; else j=nxt[j]; } return; }
函数:
inline void KMP(string s1,string s2) { getnxt(s2); int i=0,j=0; while(i<len1) { if(j==len2-1 && s1[i]==s2[j]) { printf("%d\n",i-j+1); j=nxt[j]; } if(j==-1 || s1[i]==s2[j]) i++,j++; else j=nxt[j]; } return; }
典型例题
例一 P3375 【模板】KMP字符串匹配
模板题。。。
Code
点击查看代码
#include<bits/stdc++.h> using namespace std; const int MAXN=1e6+5; int nxt[MAXN]; int len1,len2; string s1,s2; inline void getnxt(string s2) { nxt[0]=-1; int i=0,j=-1; while(i<len2) { if(j==-1 || s2[i]==s2[j]) i++,j++,nxt[i]=j; else j=nxt[j]; } return; } inline void KMP(string s1,string s2) { getnxt(s2); int i=0,j=0; while(i<len1) { if(j==len2-1 && s1[i]==s2[j]) { printf("%d\n",i-j+1); j=nxt[j]; } if(j==-1 || s1[i]==s2[j]) i++,j++; else j=nxt[j]; } return; } int main() { ios_base::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>s1>>s2; len1=s1.size(),len2=s2.size(); KMP(s1,s2); for(register int i=1;i<=len2;i++) printf("%d ",nxt[i]); return 0; }
例二 P4391
结论题。。。
Code
点击查看代码
#include<bits/stdc++.h> using namespace std; const int MAXN=1e7+5; int nxt[MAXN]; int n; int len; inline void getnxt(string s) { nxt[0]=-1; int i=0,j=-1; while(i<n) { if(j==-1 || s[i]==s[j]) i++,j++,nxt[i]=j; else j=nxt[j]; } return; } string s; int main() { ios_base::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>n; cin>>s; getnxt(s); printf("%d\n",n-nxt[n]); return 0; }
例三 P1470 最长前缀 Longest Prefix
思路:
1.将给出的串当作字典串。
2.将集合中的串当作查询串做 匹配。
3.使用差分数组,标记第一个查询串出现的位置。
4.维护前缀和,从 开始枚举,第一次没有标记的位置 ,输出答案。
Code
点击查看代码
#include<bits/stdc++.h> using namespace std; const int MAXN=1e7+5; const int N=300; int nxt[MAXN]; string B[N],s; int cnt; int dif[MAXN]; string s1; inline void getnxt(string s2) { nxt[0]=-1; int i=0,j=-1; int len2=s2.size(); while(i<len2) { if(j==-1 || s2[i]==s2[j]) i++,j++,nxt[i]=j; else j=nxt[j]; } return; } inline void KMP(string s1,string s2) { getnxt(s2); int i=0,j=0; int len1=s1.size(),len2=s2.size(); while(i<len1) { if(j==len2-1 && s1[i]==s2[j]) { int st=i-j+1; dif[st]+=1; dif[i+2]-=1; j=nxt[j]; } if(j==-1 || s1[i]==s2[j]) i++,j++; else j=nxt[j]; } return; } int main() { ios_base::sync_with_stdio(false); cin.tie(0),cout.tie(0); while(cin>>s1) { if(s1==".") break; cnt++; B[cnt]=s1; } while(cin>>s1) s=s+s1; for(register int i=1;i<=cnt;i++) KMP(s,B[i]); int len=s.size(); for(register int i=1;i<=len;i++) dif[i]+=dif[i-1]; for(register int i=1;i<=len;i++) if(dif[i]==0) { printf("%d\n",i-1); return 0; } cout<<s.size(); return 0; }
例四 CF1200E Compress Words
这道题基本的思路很容易想到,我们可以将当前需要拼接的串先放到已拼接好的串前面,那么不难发现,要求的就是这玩意儿的最长公共前后缀,那么这就是一个 求 数组的模板。
但很显然,这样做的时间复杂度为 , 会炸,所以我们考虑到,求 数组时,若串特别长,且最长公共前后缀特别短时,那么就会做很多无用的操作,就会导致 ,那么我们可以考虑只截取开头和结尾的一小部分,就可以解决时间复杂度超的问题。
这里还要注意一个点,就是在拼接时,带拼接的串可能比已拼接的串还要长,那么我们就需要从两串之间取更小的串截取。
Code
点击查看代码
#include<bits/stdc++.h> using namespace std; const int MAXN=1e6+5; int tot,len,maxn,minn; int ans,nxt[MAXN]; char s[MAXN],t[MAXN*5]; inline void getnxt(char x[]) { tot=len=strlen(x+1); minn=min(len,maxn); x[++tot]='#'; for(register int i=1;i<=minn;i++) x[++tot]=t[maxn-(minn-i)]; nxt[1]=nxt[0]=0; for(register int i=1,j=0;i<tot;i++) { j=nxt[i]; while(j && x[i+1]!=x[j+1]) j=nxt[j]; if(x[i+1]==x[j+1]) j++; nxt[i+1]=j; } for(register int i=nxt[tot]+1;i<=len;i++) t[++maxn]=x[i]; return; } int n; int main() { ios_base::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>n; for(register int i=1;i<=n;i++) { cin>>s+1; getnxt(s); } for(register int i=1;i<=maxn;i++) cout<<t[i]; return 0; }
例五 P3435 OKR-Periods of Words
不同于其他题的是,这道题要我们找的是最短公共前后缀,那么我们就只需要将最长的求出,然后不停的j=nxt[j]
的跳就行了。
Code
点击查看代码
#include<bits/stdc++.h> #define int long long using namespace std; const int MAXN=1e6+5; int n,cnt; int nxt[MAXN]; char a[MAXN]; inline void getnxt() { nxt[1]=0; int j=0; for(register int i=2;i<=n;i++) { while(j>0 && a[i]!=a[j+1]) j=nxt[j]; if(a[i]==a[j+1]) j++; nxt[i]=j; } return; } inline void KMP() { int j=0; for(register int i=1;i<=n;i++) { j=i; while(nxt[j]) j=nxt[j]; if(nxt[i]!=0) nxt[i]=j; cnt+=i-j; } return; } signed main() { ios_base::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>n; cin>>a+1; getnxt(); KMP(); printf("%lld\n",cnt); return 0; }
本文作者:Code_AC
本文链接:https://www.cnblogs.com/code-ac/p/16559885.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步