文章目录
【模板】KMP字符串匹配
题目描述
如题,给出两个字符串s1和s2,其中s2为s1的子串,求出s2在s1中所有出现的位置。
为了减少骗分的情况,接下来还要输出子串的前缀数组next。(如果你不知道这是什么意思也不要问,去百度搜[kmp算法]学习一下就知道了。)
输入格式
第一行为一个字符串,即为s1
第二行为一个字符串,即为s2
输出格式
若干行,每行包含一个整数,表示s2在s1中出现的位置
接下来1行,包括length(s2)个整数,表示前缀数组next[i]的值。
输入输出样例
输入 #1
ABABABC
ABA
输出 #1
1
3
0 0 1
代码
#include<iostream>
#include<cstring>
#define MAXN 1000010
using namespace std;
int kmp[MAXN];
int la,lb,j;
char a[MAXN],b[MAXN];
int main()
{
cin>>a+1;
cin>>b+1;
la=strlen(a+1);
lb=strlen(b+1);
for (int i=2;i<=lb;i++)
{
while(j&&b[i]!=b[j+1])
j=kmp[j];
if(b[j+1]==b[i])j++;
kmp[i]=j;
}
j=0;
for(int i=1;i<=la;i++)
{
while(j>0&&b[j+1]!=a[i])
j=kmp[j];
if (b[j+1]==a[i])
j++;
if (j==lb) {cout<<i-lb+1<<endl;j=kmp[j];}
}
for (int i=1;i<=lb;i++)
cout<<kmp[i]<<" ";
return 0;
}
【模板】扩展 KMP
题目描述
有两个字符串aaa,bbb,要求输出bbb与aaa的每一个后缀的最长公共前缀
输入格式
两行,分别为两个字符串aaa,bbb
输出格式
共两行
第一行有lenb个数,为b的next数组(特别地,
n
e
x
t
1
next_{1}
next1为lenb)
第二行有lena个数,即答案
输入输出样例
输入 #1
aaaabaa
aaaaa
输出 #1
5 4 3 2 1
4 3 2 1 0 2 1
代码
#include<bits/stdc++.h>
#define N 1000010
using namespace std;
int q,nxt[N],extend[N];
string s,t;
void getnxt()
{
nxt[0]=t.size();//nxt[0]一定是T的长度
int now=0;
while(t[now]==t[1+now]&&now+1<(int)t.size())now++;//这就是从1开始暴力
nxt[1]=now;
int p0=1;
for(int i=2;i<(int)t.size();i++)
{
if(i+nxt[i-p0]<nxt[p0]+p0)nxt[i]=nxt[i-p0];//第一种情况
else
{//第二种情况
int now=nxt[p0]+p0-i;
now=max(now,0);//这里是为了防止i>p的情况
while(t[now]==t[i+now]&&i+now<(int)t.size())now++;//暴力
nxt[i]=now;
p0=i;//更新p0
}
}
}
void exkmp()
{
getnxt();
int now=0;
while(s[now]==t[now]&&now<min((int)s.size(),(int)t.size()))now++;//暴力
extend[0]=now;
int p0=0;
for(int i=1;i<(int)s.size();i++)
{
if(i+nxt[i-p0]<extend[p0]+p0)extend[i]=nxt[i-p0];//第一种情况
else
{//第二种情况
int now=extend[p0]+p0-i;
now=max(now,0);//这里是为了防止i>p的情况
while(t[now]==s[i+now]&&now<(int)t.size()&&now+i<(int)s.size())now++;//暴力
extend[i]=now;
p0=i;//更新p0
}
}
}
int main()
{
cin>>s>>t;
exkmp();
int len=t.size();
for(int i=0;i<len;i++)printf("%d ",nxt[i]);//输出nxt
puts("");
len=s.size();
for(int i=0;i<len;i++)printf("%d ",extend[i]);//输出extend
return 0;
}
【模板】AC自动机
题目描述
给定n个模式串和1个文本串,求有多少个模式串在文本串里出现过。
输入格式
第一行一个n,表示模式串个数;
下面n行每行一个模式串;
下面一行一个文本串。
输出格式
一个数表示答案
输入输出样例
输入 #1
2
a
aa
aa
输出 #1
2
代码
#include<bits/stdc++.h>
#define N 500010
using namespace std;
queue<int>q;
struct Aho_Corasick_Automaton{
int c[N][26],val[N],fail[N],cnt;
void ins(char *s){
int len=strlen(s);int now=0;
for(int i=0;i<len;i++){
int v=s[i]-'a';
if(!c[now][v])c[now][v]=++cnt;
now=c[now][v];
}
val[now]++;
}
void build(){
for(int i=0;i<26;i++)if(c[0][i])fail[c[0][i]]=0,q.push(c[0][i]);
while(!q.empty()){
int u=q.front();q.pop();
for(int i=0;i<26;i++)
if(c[u][i])fail[c[u][i]]=c[fail[u]][i],q.push(c[u][i]);
else c[u][i]=c[fail[u]][i];
}
}
int query(char *s){
int len=strlen(s);int now=0,ans=0;
for(int i=0;i<len;i++){
now=c[now][s[i]-'a'];
for(int t=now;t&&~val[t];t=fail[t])ans+=val[t],val[t]=-1;
}
return ans;
}
}AC;
int n;char p[1000005];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%s",p),AC.ins(p);
AC.build();
scanf("%s",p);int ans=AC.query(p);
printf("%d\n",ans);
return 0;
}
[HAOI2006]受欢迎的牛|【模板】强连通分量
题目描述
每头奶牛都梦想成为牛棚里的明星。被所有奶牛喜欢的奶牛就是一头明星奶牛。所有奶牛都是自恋狂,每头奶牛总是喜欢自己的。奶牛之间的“喜欢”是可以传递的——如果A喜欢B,B喜欢C,那么A也喜欢C。牛栏里共有N 头奶牛,给定一些奶牛之间的爱慕关系,请你算出有多少头奶牛可以当明星。
输入格式
第一行:两个用空格分开的整数:N和M
第二行到第M + 1行:每行两个用空格分开的整数:A和B,表示A喜欢B
输出格式
第一行:单独一个整数,表示明星奶牛的数量
输入输出样例
输入 #1
3 3
1 2
2 1
2 3
输出 #1
1
代码
#include<bits/stdc++.h>
#define N 10050
using namespace std;
struct EDGE{
int next,to;
}edge[N*20];
int head[20*N],dfn[N],low[N];
int du[N],id[N],all[N];
bool insta[N];int cnt,tot,gg,n,m;
stack<int>s;
inline void add(int x,int y){
cnt++;
edge[cnt].to=y;
edge[cnt].next=head[x];
head[x]=cnt;
}
void in(int &read){
int x=0,f=1;char ch;
for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
if(ch=='-'){f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
read=x*f;//可以处理负数的读入优化
}
void tarjan(int x){
dfn[x]=low[x]=++tot;
s.push(x);insta[x]=true;
for(int i=head[x];i;i=edge[i].next){
int u=edge[i].to;
if(!dfn[u]){
tarjan(u);
low[x]=min(low[x],low[u]);
}
else if(insta[u])low[x]=min(low[x],dfn[u]);
}//tarjan模板
int k;
if(low[x]==dfn[x]){
++gg;
do{
k=s.top();s.pop();
insta[k]=false;
id[k]=gg;all[gg]++;//将一个分量中的元素染成一色
}while(x!=k);
}
}
int main(){
in(n);in(m);
int a,b;
for(register int i=1;i<=m;i++){
in(a);in(b);
add(a,b);
}
for(register int i=1;i<=n;i++)
if(!dfn[i])tarjan(i);
for(register int w=1;w<=n;w++){
for(int i=head[w];i;i=edge[i].next){
int u=edge[i].to;
if(id[w]!=id[u]){
du[id[w]]++;//遍历每一个点并记录出度
}
}
}
int tt=0;
for(register int i=1;i<=gg;i++)
if(!du[i]){
if(tt){puts("0");return 0;}//两次出现出度为0直接输出0
tt=i;//记录出度为零的分量的边号
}
printf("%d\n",all[tt]);
return 0;
}
RMQ模板
求区间的最大值和最小值
#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
using namespace std;
const int MAXN = 100117;
int n,query;
int num[MAXN];
int F_Min[MAXN][20],F_Max[MAXN][20];
void Init()
{
for(int i = 1; i <= n; i++)
{
F_Min[i][0] = F_Max[i][0] = num[i];
}
for(int i = 1; (1<<i) <= n; i++) //按区间长度递增顺序递推
{
for(int j = 1; j+(1<<i)-1 <= n; j++) //区间起点
{
F_Max[j][i] = max(F_Max[j][i-1],F_Max[j+(1<<(i-1))][i-1]);
F_Min[j][i] = min(F_Min[j][i-1],F_Min[j+(1<<(i-1))][i-1]);
}
}
}
int Query_max(int l,int r)
{
int k = (int)(log(double(r-l+1))/log((double)2));
return max(F_Max[l][k], F_Max[r-(1<<k)+1][k]);
}
int Query_min(int l,int r)
{
int k = (int)(log(double(r-l+1))/log((double)2));
return min(F_Min[l][k], F_Min[r-(1<<k)+1][k]);
}
int main()
{
int a,b;
scanf("%d %d",&n,&query);
for(int i = 1; i <= n; i++)
scanf("%d",&num[i]);
Init();
while(query--)
{
scanf("%d %d",&a,&b);
printf("区间%d到%d的最大值为:%d\n",a,b,Query_max(a,b));
printf("区间%d到%d的最小值为:%d\n",a,b,Query_min(a,b));
printf("区间%d到%d的最大值和最小值只差为:%d\n",a,b,Query_max(a,b)-Query_min(a,b));
}
return 0;
}
求区间内出现次数最多的数字出现的次数
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
const int maxn = 100017;
int num[maxn], f[maxn], MAX[maxn][20];
int n;
int max(int a,int b)
{
return a>b ? a:b;
}
int rmq_max(int l,int r)
{
if(l > r)
return 0;
int k = log((double)(r-l+1))/log(2.0);
return max(MAX[l][k],MAX[r-(1<<k)+1][k]);
}
void init()
{
for(int i = 1; i <= n; i++)
{
MAX[i][0] = f[i];
}
int k = log((double)(n+1))/log(2.0);
for(int i = 1; i <= k; i++)
{
for(int j = 1; j+(1<<i)-1 <= n; j++)
{
MAX[j][i] = max(MAX[j][i-1],MAX[j+(1<<(i-1))][i-1]);
}
}
}
int main()
{
int a, b, q;
while(scanf("%d",&n) && n)
{
scanf("%d",&q);
for(int i = 1; i <= n; i++)
{
scanf("%d",&num[i]);
}
sort(num+1,num+n+1);
for(int i = 1; i <= n; i++)
{
if(i == 1)
{
f[i] = 1;
continue;
}
if(num[i] == num[i-1])
{
f[i] = f[i-1]+1;
}
else
{
f[i] = 1;
}
}
init();
for(int i = 1; i <= q; i++)
{
scanf("%d%d",&a,&b);
int t = a;
while(t<=b && num[t]==num[t-1])
{
t++;
}
int cnt = rmq_max(t,b);
int ans = max(t-a,cnt);
printf("%d\n",ans);
}
}
return 0;
}
/*
10 3
-1 -1 1 2 1 1 1 10 10 10
2 3
1 10
5 10
*/
倍增求LCA
题目描述
如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。
输入格式
第一行包含三个正整数N、M、S,分别表示树的结点个数、询问的个数和树根结点的序号。
接下来N-1行每行包含两个正整数x、y,表示x结点和y结点之间有一条直接连接的边(数据保证可以构成树)。
接下来M行每行包含两个正整数a、b,表示询问a结点和b结点的最近公共祖先。
输出格式
输出包含M行,每行包含一个正整数,依次为每一个询问的结果。
输入输出样例
输入 #1
5 5 4
3 1
2 4
5 1
1 4
2 4
3 2
3 5
1 2
4 5
输出 #1
4
4
1
4
4
代码
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int maxn=500000+2;
int n,m,s;
int k=0;
int head[maxn],d[maxn],p[maxn][21];//head数组就是链接表标配了吧?d存的是深度(deep),p[i][j]存的[i]向上走2的j次方那么长的路径
struct node{
int v,next;
}e[maxn*2];//存树
void add(int u,int v)
{
e[k].v=v;
e[k].next=head[u];
head[u]=k++;
} //加边函数
void dfs(int u,int fa)
{
d[u]=d[fa]+1;
p[u][0]=fa;
for(int i=1;(1<<i)<=d[u];i++)
p[u][i]=p[p[u][i-1]][i-1];
for(int i=head[u];i!=-1;i=e[i].next)
{
int v=e[i].v;
if(v!=fa)
dfs(v,u);
}
} //首先进行的预处理,将所有点的deep和p的初始值dfs出来
int lca(int a,int b) //非常标准的lca查找
{
if(d[a]>d[b])
swap(a,b); //保证a是在b结点上方,即a的深度小于b的深度
for(int i=20;i>=0;i--)
if(d[a]<=d[b]-(1<<i))
b=p[b][i]; //先把b移到和a同一个深度
if(a==b)
return a; //特判,如果b上来和就和a一样了,那就可以直接返回答案了
for(int i=20;i>=0;i--)
{
if(p[a][i]==p[b][i])
continue;
else
a=p[a][i],b=p[b][i]; //A和B一起上移
}
return p[a][0]; 找出最后a值的数字
}
int main()
{
memset(head,-1,sizeof(head));
int a,b;
scanf("%d%d%d",&n,&m,&s);
for(int i=1;i<n;i++)
{
scanf("%d%d",&a,&b);
add(a,b);
add(b,a); //无向图,要加两次
}
dfs(s,0);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&a,&b);
printf("%d\n",lca(a,b));
}
return 0;
}
组合数取模
#include<iostream>
#include<cstdio>
#include<ctime>
#include<cstring>
#include<cstdlib>
#include<vector>
#define LL __int64
using namespace std;
LL PowMod(LL a,LL b,LL MOD){ //费马小定理求逆元
LL ret=1;
while(b){
if(b&1) ret=(ret*a)%MOD;
a=(a*a)%MOD;
b>>=1;
}
return ret;
}
LL fac[100005];
LL Get_Fact(LL p){
fac[0]=1;
for(LL i=1;i<=p;i++)
fac[i]=(fac[i-1]*i)%p; //预处理阶乘
}
LL Lucas(LL n,LL m,LL p){
LL ret=1;
while(n&&m){
LL a=n%p,b=m%p;
if(a<b) return 0;
ret=(ret*fac[a]*PowMod(fac[b]*fac[a-b]%p,p-2,p))%p;
n/=p;
m/=p;
}
return ret;
}
int main(){
int t;
scanf("%d",&t);
while(t--){
LL n,m,p;
scanf("%I64d%I64d%I64d",&n,&m,&p);
Get_Fact(p);
printf("%I64d\n",Lucas(n,m,p));
}
return 0;
}
/*
卢卡斯定理
O(logp(n))
*/
平衡树
// luogu-judger-enable-o2
#include<iostream>
#include<cstdio>
using namespace std;
#define MAXN 1000000
int f[MAXN],cnt[MAXN],value[MAXN],sons[MAXN][2],sub_size[MAXN],whole_size,root;
inline int qread(){
int res=0,k=1;
char c=getchar();
while(!isdigit(c)){
if(c=='-')k=-1;
c=getchar();
}
while(isdigit(c)){
res=(res<<1)+(res<<3)+c-48;
c=getchar();
}
return res*k;
}
inline void S_Clear(int x){
sons[x][0]=sons[x][1]=f[x]=sub_size[x]=cnt[x]=value[x]=0;
}
inline bool get_which(int x){
return sons[f[x]][1]==x;
}
inline void update(int x){
if (x){
sub_size[x]=cnt[x];
if (sons[x][0]) sub_size[x]+=sub_size[sons[x][0]];
if (sons[x][1]) sub_size[x]+=sub_size[sons[x][1]];
}
return ;
}
inline void rotate(int x){
int father=f[x],g_father=f[father],which_son=get_which(x);
sons[father][which_son]=sons[x][which_son^1];
f[sons[father][which_son]]=father;
sons[x][which_son^1]=father;
f[father]=x;
f[x]=g_father;
if(g_father){
sons[g_father][sons[g_father][1]==father]=x;
}
update(father);
update(x);
}
inline void splay(int x){
for (int fa;fa=f[x];rotate(x))
if (f[fa])
rotate((get_which(x)==get_which(fa))?fa:x);
root=x;
}
inline void insert(int x){
if(!root){
whole_size++;
sons[whole_size][0]=sons[whole_size][1]=f[whole_size]=0;
root=whole_size;
sub_size[whole_size]=cnt[whole_size]++;
value[whole_size]=x;
return ;
}
int now=root,fa=0;
while(1){
if(x==value[now]){
cnt[now]++;
update(now);
update(fa);
splay(now);
break;
}
fa=now;
now=sons[now][value[now]<x];
if(!now){
whole_size++;
sons[whole_size][0]=sons[whole_size][1]=0;
f[whole_size]=fa;
sub_size[whole_size]=cnt[whole_size]=1;
sons[fa][value[fa]<x]=whole_size;
value[whole_size]=x;
update(fa);
splay(whole_size);
break;
}
}
}
inline int find_num(int x){
int now=root;
while(1){
if(sons[now][0]&&x<=sub_size[sons[now][0]])
now=sons[now][0];
else {
int temp=(sons[now][0]?sub_size[sons[now][0]]:0)+cnt[now];
if(x<=temp)return value[now];
x-=temp;
now=sons[now][1];
}
}
}
inline int find_rank(int x){
int now=root,ans=0;
while(1){
if (x<value[now])
now=sons[now][0];
else{
ans+=(sons[now][0]?sub_size[sons[now][0]]:0);
if (x==value[now]){
splay(now); return ans+1;
}
ans+=cnt[now];
now=sons[now][1];
}
}
}
inline int find_pre(){
int now=sons[root][0];
while(sons[now][1])now=sons[now][1];
return now;
}
inline int find_suffix(){
int now=sons[root][1];
while(sons[now][0])now=sons[now][0];
return now;
}
inline void my_delete(int x){
int hhh=find_rank(x);
if (cnt[root]>1){
cnt[root]--;
update(root);
return;
}
if (!sons[root][0]&&!sons[root][1]) {
S_Clear(root);
root=0;
return;
}
if (!sons[root][0]){
int old_root=root;
root=sons[root][1];
f[root]=0;
S_Clear(old_root);
return;
}
else if (!sons[root][1]){
int old_root=root;
root=sons[root][0];
f[root]=0;
S_Clear(old_root);
return;
}
int left_max=find_pre(),old_root=root;
splay(left_max);
sons[root][1]=sons[old_root][1];
f[sons[old_root][1]]=root;
S_Clear(old_root);
update(root);
}
int main(){
int m,num,be_dealt;
cin>>m;
for(int i=1;i<=m;i++){
num=qread();
be_dealt=qread();
switch(num)
{
case 1:insert(be_dealt);break;
case 2:my_delete(be_dealt);break;
case 3:printf("%d\n",find_rank(be_dealt));break;
case 4:printf("%d\n",find_num(be_dealt));break;
case 5:insert(be_dealt);printf("%d\n",value[find_pre()]);my_delete(be_dealt);break;
case 6:insert(be_dealt);printf("%d\n",value[find_suffix()]);my_delete(be_dealt);break;
}
}
return 0;
}
线段树
/*不怕比我聪明的人,只怕比我聪明但比我还要努力的人*/
#include<iostream>
#include<cstdio>
#define INF 99999999
using namespace std;
typedef long long LL;
struct node
{
LL val;
LL len;
LL lazy;
LL l,r;
}tree[300005];
LL arr[500005];
LL n,m;
void build(LL root,LL l,LL r) //建树
{
LL mid;
tree[root].lazy=0;
tree[root].l=l;tree[root].r=r;
tree[root].len=r-l+1;
if (l==r) tree[root].val=arr[l];
else
{
mid=(l+r)/2;
build(root*2,l,mid);
build(root*2+1,mid+1,r);
tree[root].val=tree[root*2].val+tree[root*2+1].val;
}
}
void pushdown(LL root) //向下传递lazy标记
{
if (tree[root].lazy)
{
tree[root*2].lazy+=tree[root].lazy;
tree[root*2+1].lazy+=tree[root].lazy;
tree[root*2].val+=tree[root*2].len*tree[root].lazy;
tree[root*2+1].val+=tree[root*2+1].len*tree[root].lazy;
tree[root].lazy=0;
}
}
void add(LL root,LL id,LL addval) //单点更新
{
LL mid;
if (tree[root].l==tree[root].r)
{
tree[root].val+=addval;
return;
}
else
{
mid=(tree[root].l+tree[root].r)/2;
if (id<=mid) add(root*2,id,addval);
else add(root*2+1,id,addval);
tree[root].val=tree[root*2].val+tree[root*2+1].val;
}
}
LL query(LL root,LL l,LL r) //计算区间和
{
LL mid;
if (tree[root].l>=l&&tree[root].r<=r)
return tree[root].val;
if (tree[root].l>r||tree[root].r<l)
return 0;
if (tree[root].lazy) pushdown(root);
return query(root*2,l,r)+query(root*2+1,l,r);
}
void update(LL root,LL l,LL r,LL addval) //区间更新
{
LL mid;
if (tree[root].l>=l&&tree[root].r<=r)
{
tree[root].lazy+=addval;
tree[root].val+=tree[root].len*addval;
return;
}
if (tree[root].l>r||tree[root].r<l)
return;
if (tree[root].lazy) pushdown(root);
update(root*2,l,r,addval);
update(root*2+1,l,r,addval);
tree[root].val=tree[root*2].val+tree[root*2+1].val;
}
int main()
{
LL i,x,y,z,k;
scanf("%d%d",&n,&m);
for (i=1;i<=n;i++)
{
scanf("%lld",&arr[i]);
}
build(1,1,n);
for (i=1;i<=m;i++)
{
scanf("%lld",&z);
if (z==1)
{
scanf("%lld%lld%lld",&x,&y,&k);
update(1,x,y,k);
}
else
{
scanf("%lld%lld",&x,&y);
printf("%lld\n",query(1,x,y));
}
}
}
只需要在建树的时候注意tree[].val存储的是每个节点区间的最大值或最小值即可求区间最大/最小值。
int ask(int root,int l,int r)
{
int mid;
if (tree[root].l==l&&tree[root].r==r)
return tree[root].val;
else
{
mid=(tree[root].l+tree[root].r)/2;
if (mid>=r)
return ask(root*2,l,r);
else if (mid<l)
return ask(root*2+1,l,r);
else return ask(root*2,l,mid)+ask(root*2+1,mid+1,r);
}
}
本文整理了OI竞赛中常用的字符串匹配算法KMP及其扩展,AC自动机,以及解决区间查询问题的模板,如RMQ、LCA等。通过这些模板,可以帮助参赛者快速解决相关问题。
6

被折叠的 条评论
为什么被折叠?



