字符串学习笔记
KMP
模板题
OI-wiki上有一个很不错的kmp做法,就是直接把模式串与文本串用特殊符号链接,然后求前缀数组即可
感觉vector可能更舒服(?)
#include<iostream> #include<cstdio> #include<cstring> #include<vector> using namespace std; const int N=2000000; char a[N],b[N]; vector<char>c; vector<int>kmp; void solve(){ scanf("%s%s",a,b); int la=strlen(a),lb=strlen(b); for(int i=0;i<lb;++i) c.push_back(b[i]); c.push_back('\0'); for(int i=0;i<la;++i) c.push_back(a[i]); kmp.push_back(0); for(int i=1;i<=la+lb;++i){ int j=kmp[i-1]; while(j>0&&c[i]!=c[j]) j=kmp[j-1]; if(c[i]==c[j]) kmp.push_back(j+1); else kmp.push_back(j); if(kmp[i]==lb) printf("%d\n",i-2*lb+1); } for(int i=0;i<lb;++i) printf("%d ",kmp[i]); } int main(){ solve(); return 0; }
Manacher
模板题
跟KMP一样,本质都是用已更新状态来减少未知状态的计算
注意边界
#include<iostream> #include<cstdio> #include<cstring> #include<vector> using namespace std; vector<char>a; vector<int>p; void init(){ a.push_back('%');a.push_back('|'); char c; while(scanf("%c",&c)!=EOF){ a.push_back(c); a.push_back('|'); } a.push_back('$'); } void manacher(){ init(); p.push_back(1); int sz=a.size(),ans=0; for(int i=1,mid=0,r=0;i<sz;++i){ if(i<=r) p.push_back(min(p[mid*2-i],r-i+1)); else p.push_back(1); while(a[i-p[i]]==a[i+p[i]]) ++p[i]; if(p[i]+i>r) r=p[i]+i-1,mid=i; ans=max(ans,p[i]); } printf("%d\n",ans-1); } int main(){ manacher(); return 0; }
回文匹配
KMP+Manacher+二阶前缀和
1.二阶前缀和的数组最前面需要两个0,因为
这四项的下届分别相对原来的0进行了-0,-1,-1,-2的处理,所以要流出两个空位置
2.前缀和的这个问题卡了我一晚上( 任何时候进行前缀和都不要忘了判断上下界的大小。
#include<bits/stdc++.h> #define ll long long using namespace std; const int N=10000000; string s1,s2;int n1,n2; int num[N],f[N]; void KMP(){ string s3=s2+"$"+s1;int sz=n1+n2+1; vector<int> kmp;kmp.clear();kmp.push_back(0); for(int i=1;i<sz;++i){ int j=kmp[i-1]; while(j>0&&s3[i]!=s3[j]) j=kmp[j-1]; if(s3[i]==s3[j]) kmp.push_back(j+1); else kmp.push_back(j); if(kmp[i]==n2) num[i-n2-1]++; } for(int i=1;i<n1;++i) num[i]+=num[i-1]; for(int i=1;i<n1;++i) num[i]+=num[i-1]; for(int i=n1+1;i>=2;--i) num[i]=num[i-2]; } void Manacher(){ string s4="$"+s1+"%"; vector<int> p;p.push_back(1); for(int i=1,mid=0,r=0;i<n1+2;++i){ if(i<=r) p.push_back(min(p[mid*2-i],r-i+1)); else p.push_back(1); while(s4[i-p[i]]==s4[i+p[i]]) ++p[i]; if(i+p[i]>r){ r=i+p[i]-1;mid=i; } } for(int i=2;i<=n1+1;++i) f[i]=p[i-1]; } void solve(){ unsigned int ans=0;num[0]=num[1]=0; // for(int i=1;i<=n1;++i) // for(int j=n2/2+1;j<=f[i];++j) // ans+=num[i+j-1]-num[i-j+n2-1]; for(int i=2;i<=n1+1;++i) ans+=num[i+f[i]-1]-num[min(i+f[i]-1,i+n2/2-1)]-num[i-n2/2+n2-2]+num[min(i-n2/2+n2-2,i-f[i]+n2-2)]; cout<<ans<<endl; } int main(){ cin>>n1>>n2>>s1>>s2; KMP();Manacher();solve(); return 0; }
Trie
于是他错误的点名开始了
两种建树方法,使用数组模拟和直接使用指针(好像没啥区别),使用vector能有效避免数组不知道开多大的问题
//静态数组 #include<bits/stdc++.h> #define int long long using namespace std; int n,m; struct TRIE1{ int cnt; int trie[1000000][30]; int end[1000000]; void insert(string a){ int u=0,len=a.length(); for(int j=0;j<len;++j){ if(!trie[u][a[j]-'a']) trie[u][a[j]-'a']=++cnt; u=trie[u][a[j]-'a']; }end[u]++; } void fnd(string a){ int u=0,len=a.length(); for(int j=0;j<len;++j){ int num=a[j]-'a'; if(!trie[u][num]){ printf("WRONG\n");return; }u=trie[u][num]; }if(end[u]==1){ end[u]++;printf("OK\n"); }else if(end[u]==0) printf("WRONG\n"); else printf("REPEAT\n"); } }Trie; signed main(){ cin>>n;for(int i=1;i<=n;++i){ string tmp;cin>>tmp;Trie.insert(tmp); }cin>>m;for(int i=1;i<=m;++i){ string tmp;cin>>tmp;Trie.fnd(tmp); } return 0; }
//vector #include<bits/stdc++.h> #define int long long using namespace std; int n,m,cnt; struct node{ int son[30],end; // int fa; };vector<node>trie; void build(string a){ int u=0,len=a.length(); for(int i=0;i<len;++i){ int num=a[i]-'a'; if(!trie[u].son[num]){ trie[u].son[num]=++cnt; trie.push_back({NULL,0}); } u=trie[u].son[num]; } ++trie[u].end; } void fnd(string a){ int u=0,len=a.length(); for(int i=0;i<len;++i){ int num=a[i]-'a'; if(!trie[u].son[num]){cout<<"WRONG"<<endl;return;} u=trie[u].son[num]; } if(trie[u].end==1) {++trie[u].end;cout<<"OK"<<endl;} else if(trie[u].end==0) cout<<"WRONG"<<endl; else cout<<"REPEAT"<<endl; } signed main(){ node tmp={NULL,0}; trie.push_back({NULL,0}); cin>>n;for(int i=1;i<=n;++i){string tmp;cin>>tmp;build(tmp);} cin>>m;for(int i=1;i<=m;++i){string tmp;cin>>tmp;fnd(tmp);} return 0; }
最长异或路径
思路:先将边权异或和最大转换为两个节点到树根的边权异或和异或最大,然后就转换成了n个数任意两个数的异或值最大。对这n个数建01-Trie
,然后对每个数进行比较:从Trie的树根开始向下走,每次尽量选择与数字相反的数位进行贪心,最终得到的就是正确答案。复杂度
注意:无向图开双倍数组,Trie开30倍数组!
#include <bits/stdc++.h> using namespace std; const int N = 400000; int n, cnt, ans; struct edge{ int nxt, v, w; }e[N]; int h[N], val[N]; struct trie{ int son[2] = {0, 0}; }a[4000000]; void add(int u, int v, int w){ e[++cnt].nxt = h[u]; e[cnt].v = v; e[cnt].w = w; h[u] = cnt; } void dfs(int u, int fa){ for(int i = h[u]; i; i = e[i].nxt){ int v = e[i].v, w = e[i].w; if(v == fa) continue; val[v] = val[u] ^ w; dfs(v, u); } } void build(){ cnt = 0;int now; for(int i = 1; i <= n; ++i){ now = 0; for(int j = (1 << 30); j; j >>= 1){ bool x = j & val[i]; if(!a[now].son[x]) a[now].son[x] = ++cnt; now = a[now].son[x]; } } } int maxxor(int val){ int now = 0, ret = 0; for(int j = (1<<30); j; j >>= 1){ bool x = j & val; if(a[now].son[!x]){ now = a[now].son[!x]; ret += j; } else now = a[now].son[x]; }return ret; } signed main(){ cin>>n; for(int i = 1; i < n; ++i){ int u, v, w;cin>>u>>v>>w;add(u, v, w);add(v, u, w); }dfs(1, 0);build(); for(int i = 1; i <= n; ++i){ ans = max(ans, maxxor(val[i])); }cout<<ans<<endl; system("pause"); return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效