字符串学习笔记
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,因为
\[\sum_{i=0}^{n-1}\sum_{j=m/2+1}^{f_i}\sum_{k=i-j+m}^{i+j-1}a_k
\]
\[=\sum_{i=0}^{n-1}\sum_{j=m/2+1}^{f_i}(pre_{i+j-1}-pre_{i-j+m-1})
\]
\[=\sum_{i=0}^{n-1}((num_{i+f_i-1}-num_{i+m/2-1})-(num_{i-m/2+m-2}-num_{i-f_i+m-2}))
\]
这四项的下届分别相对原来的0进行了-0,-1,-1,-2的处理,所以要流出两个空位置
2.前缀和的\(O(n^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的树根开始向下走,每次尽量选择与数字相反的数位进行贪心,最终得到的就是正确答案。复杂度\(O(nlogn)\)
注意:无向图开双倍数组,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;
}