字符串杂记I
AC自动机
CF710F String Set Queries
洛谷题目传送门
询问出现次数是AC自动机的拿手好戏,一个节点的出现次数是这个节点在失陪树上到根节点的路径上的出现次数总和
但是AC自动机这种东西不支持凭空添加一个字符串
如果可以离线就可以用CDQ分治搞定
但是强制在线就有点困难
有一个可以解决强制在线问题的分治算法,就是二进制分组
我们分别维护大小为\(1,2,4,8,16……\)的\(AC\)自动机
加入一个串的时候,我们就先将它和大小为1的合并,重构AC自动机,发现大小和下一组的大小相等
那么就和下一组合并,已知持续下去知道不能继续合并
答案即为在所有AC自动机的答案之和
分析复杂度
查询复杂度\(O(qlogq)\)
合并次数复杂度\(O(qlogq)\)
重构复杂度,因为每个串只会被重构\(logq\)次,复杂度\(O(\sum|s|logq)\)
默认\(\sum|s|\)和\(q\)同阶,复杂度\(O(qlogq)\)
但是还有删除,观察到匹配次数具有可减性,因此我们考虑再开一个删除的串的二进制分组
我们用答案在所有串的出现次数减去删除中的出现次数
就是答案了
常数有点大,可以用垃圾回收减少空间复杂度
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 6e5+7;
vector<int> str[N];
int len[N];
int n;
char s[N];
struct node
{
int tr[N][28];
int Next[N];
LL cnt[N];
int rot[N];
int top;
int siz[N],seq[30][N];
int bin[N],pos;
bool ins[N];
void Insert(int r,int x)
{
int p=rot[r];
for(int i=1;i<=len[x];i++)
{
int c=str[x][i];
if(tr[p][c]==rot[r])
{
ins[bin[pos]]=0;
tr[p][c]=bin[pos--];
for(int ch=0;ch<26;ch++)
tr[tr[p][c]][ch]=rot[r];
}
p=tr[p][c];
}
cnt[p]++;
}
void init()
{
memset(tr,0,sizeof(tr));
memset(Next,0,sizeof(Next));
memset(cnt,0,sizeof(cnt));
memset(rot,0,sizeof(rot));
memset(seq,0,sizeof(seq));
top=pos=0;
for(int i=(N-10);i>=1;i--)
{
bin[++pos]=i;
ins[i]=1;
}
}
queue<int> q;
void Build(int r)
{
Next[rot[r]]=rot[r];
for(int c=0;c<26;c++)
if(tr[rot[r]][c]!=rot[r])
{
q.push(tr[rot[r]][c]);
Next[tr[rot[r]][c]]=rot[r];
}
while(!q.empty())
{
int x=q.front();
q.pop();
for(int c=0;c<26;c++)
{
int y=tr[x][c];
if(y==rot[r]) tr[x][c]=tr[Next[x]][c];
else
{
Next[y]=tr[Next[x]][c];
cnt[y]+=cnt[Next[y]];
q.push(y);
}
}
}
}
void clear(int r)
{
Pick(rot[r],rot[r]);
for(int c=0;c<26;c++)
tr[rot[r]][c]=rot[r];
}
void Exclear(int r)
{
clear(r);
ins[rot[r]]=1;
bin[++pos]=rot[r];
cnt[rot[r]]=Next[rot[r]]=0;
siz[r]=0;
for(int c=0;c<26;c++)
tr[rot[r]][c]=0;
rot[r]=0;
top--;
}
void Pick(int x,int r)
{
for(int c=0;c<26;c++)
{
int y=tr[x][c];
if(ins[y]||y==r) continue;
cnt[y]=Next[y]=0;
ins[y]=1;
bin[++pos]=y;
Pick(y,r);
tr[x][c]=r;
}
}
void Merge(int A,int B)
{
clear(A);
for(int i=1;i<=siz[B];i++)
seq[A][++siz[A]]=seq[B][i];
for(int i=1;i<=siz[A];i++)
Insert(A,seq[A][i]);
Build(A);
Exclear(B);
}
void Addin(int x)
{
int r=++top;
siz[r]=1;
seq[r][1]=x;
ins[bin[pos]]=0;
rot[r]=bin[pos--];
for(int c=0;c<26;c++)
tr[rot[r]][c]=rot[r];
Insert(r,x);
Build(r);
while(siz[r]==siz[r-1])
{
Merge(r-1,r);
r--;
}
}
LL Ask()
{
int m=strlen(s+1);
LL res=0;
for(int r=1;r<=top;r++)
{
int p=rot[r];
for(int i=1;i<=m;i++)
{
int c=s[i]-'a';
p=tr[p][c];
res+=cnt[p];
}
}
return res;
}
}Add,Del;
int idx=0;
int main()
{
// freopen("b.in","r",stdin);
// freopen("b.out","w",stdout);
cin>>n;
Add.init();
Del.init();
int cnt=0;
for(int p=1;p<=n;p++)
{
int t;
scanf("%d",&t);
if(t==1)
{
scanf("%s",s+1);
++idx;
len[idx]=strlen(s+1);
str[idx].push_back(0);
int m=strlen(s+1);
for(int i=1;i<=m;i++)
str[idx].push_back(s[i]-'a');
Add.Addin(idx);
cnt++;
}
else if(t==2)
{
scanf("%s",s+1);
++idx;
str[idx].push_back(0);
len[idx]=strlen(s+1);
int m=strlen(s+1);
for(int i=1;i<=m;i++)
str[idx].push_back(s[i]-'a');
Del.Addin(idx);
}
else
{
scanf("%s",s+1);
printf("%lld\n",Add.Ask()-Del.Ask());
}
fflush(stdout);
}
return 0;
}
CF587F Duff is Mad
洛谷题目传送门
考虑根号分治
设\(m=\sum|s|\)
如果\(|s_k|\leq\sqrt m\),那么这样的长度很少,离线之后可以暴力做
具体的做法就是把询问离线,把\(l,r\)换成前缀相减的样子
然后把\(k\)挂在\(s_{l-1}\)和\(s_r\)下面
问题变成求\(s_1-s_i\)在\(s_k\)中出现的次数
然后建出AC自动机
遍历所有的字符串,在这个字符串对应的节点的子树的所有权值就要加一
然后遍历属于这个字符串的询问,查询这个询问在\(AC\)自动机上的答案
也就是在AC自动机中跑一遍,当前节点的权值就是出现次数
我们需要一个区间加,单点求值的数据结构
如果用树状数组,会加上一个log,因此,我们用分块
复杂度\(O(n\sqrt m+q\sqrt m)\)
如果\(|s_k|>\sqrt m\),那么串的个数很少,我们可以对于每个这样的串分别处理
首先把\(s_k\)在AC自动机上跑一边,把遍历的节点的权值加一
然后类似的转化为前缀和相减的形式
把线性扫过\(n\)个串,把这个串在AC自动机上跑一遍,那么这个某个节点在\(s_k\)的出现次数就是他在失配树上子树的权值之和,可以直接在AC自动机上做一遍子树和就行了
复杂度\(O(m\sqrt m)\)
#include<bits/stdc++.h>
using namespace std;
const int N = 5e5+7;
const int S = 400;
typedef long long LL;
int tr[N][27],Next[N];
vector<int> str[N];
int len[N],End[N];
int tot=0;
void Insert(int x)
{
int p=0;
for(int i=1;i<=len[x];i++)
{
int c=str[x][i];
if(!tr[p][c]) tr[p][c]=++tot;
p=tr[p][c];
}
End[x]=p;
}
queue<int> q;
void Build()
{
for(int c=0;c<26;c++)
if(tr[0][c]) q.push(tr[0][c]);
while(!q.empty())
{
int x=q.front();
q.pop();
for(int c=0;c<26;c++)
{
int y=tr[x][c];
if(!y) tr[x][c]=tr[Next[x]][c];
else
{
Next[y]=tr[Next[x]][c];
q.push(y);
}
}
}
}
int n,m;
char s[N];
int B;
struct Query
{
int x,tp,id;
};
vector<Query> Q1[N],Q2[N];
struct edge
{
int y,next;
}e[2*N];
int link[N],t=0;
void add(int x,int y)
{
e[++t].y=y;
e[t].next=link[x];
link[x]=t;
}
int st[N],ed[N],siz[N];
int top=0;
int seq[N];
void dfs(int x)
{
siz[x]=1;
st[x]=++top;
seq[top]=x;
for(int i=link[x];i;i=e[i].next)
{
int y=e[i].y;
dfs(y);
siz[x]+=siz[y];
}
ed[x]=top;
}
int L[N],R[N],cnt=0;
int bel[N];
LL a[N],tag[N];
void Blocks()
{
for(int i=1;i<=tot+1;i++)
{
bel[i]=(i-1)/B+1;
R[bel[i]]=i;
if(!L[bel[i]]) L[bel[i]]=i;
cnt=max(cnt,bel[i]);
}
}
void Add(int l,int r,int v)
{
if(bel[r]-bel[l]<=1)
{
for(int i=l;i<=r;i++)
a[i]+=v;
return;
}
else
{
for(int i=l;i<=R[bel[l]];i++) a[i]+=v;
for(int i=L[bel[r]];i<=r;i++) a[i]+=v;
for(int i=bel[l]+1;i<bel[r];i++) tag[i]+=v;
return;
}
}
LL Ans[N];
bool cmp(Query a,Query b)
{
return a.x<b.x;
}
int main()
{
cin>>n>>m;
for(int x=1;x<=n;x++)
{
scanf("%s",s+1);
str[x].push_back(0);
len[x]=strlen(s+1);
for(int i=1;i<=len[x];i++)
str[x].push_back(s[i]-'a');
Insert(x);
}
Build();
B=sqrt(tot*1.0+1);
for(int i=1;i<=m;i++)
{
int l,r,x;
scanf("%d %d %d",&l,&r,&x);
if(len[x]>B)
{
if(l!=1) Q2[x].push_back((Query){l-1,-1,i});
Q2[x].push_back((Query){r,1,i});
}
else
{
if(l!=1) Q1[l-1].push_back((Query){x,-1,i});
Q1[r].push_back((Query){x,1,i});
}
}
for(int i=1;i<=tot;i++)
add(Next[i],i);
dfs(0);
Blocks();
for(int i=1;i<=n;i++)
{
Add(st[End[i]],ed[End[i]],1);
for(int p=0;p<(int)Q1[i].size();p++)
{
int x=Q1[i][p].x,tp=Q1[i][p].tp,id=Q1[i][p].id;
int r=0;
for(int j=1;j<=len[x];j++)
{
int c=str[x][j];
r=tr[r][c];
Ans[id]+=1ll*(a[st[r]]+tag[bel[st[r]]])*tp;
}
}
}
for(int x=1;x<=n;x++)
{
if(Q2[x].size())
{
memset(a,0,sizeof(a));
int p=0;
for(int i=1;i<=len[x];i++)
{
int c=str[x][i];
p=tr[p][c];
a[p]++;
}
sort(Q2[x].begin(),Q2[x].end(),cmp);
for(int i=top;i>=2;i--)
a[Next[seq[i]]]+=a[seq[i]];
int j=1;
LL sum=0;
for(p=0;p<(int)Q2[x].size();p++)
{
int y=Q2[x][p].x;
while(j<=y) sum+=a[End[j++]];
Ans[Q2[x][p].id]+=1ll*sum*Q2[x][p].tp;
}
}
}
for(int i=1;i<=m;i++)
printf("%lld\n",Ans[i]);
return 0;
}
后缀自动机(SAM)
CF666E Forensic Examination
洛谷题目传送门
考虑把\(S\)和所有的\(T\)一起建成广义后缀自动机
然后再每个节点维护一个线段树,表示这个节点代表的字串在每个\(T\)中出现的次数
可以在\(parent\)树上线段树合并就可以了
查询的时候先树上倍增找到这个区间对应的节点然后在线段树上做就可以了
#include<bits/stdc++.h>
using namespace std;
#define PII pair<int,int>
#define mk(x,y) make_pair(x,y)
#define X(x) x.first
#define Y(x) x.second
const int S = 6e5+7;
const int N = 2*S;
int tr[N][26],len[N],fa[N];
int tot=0;
int n,m;
int sum=0;
void copy(int x,int y)
{
len[x]=len[y];
fa[x]=fa[y];
for(int c=0;c<26;c++)
tr[x][c]=tr[y][c];
}
int nodes[S];
int Extend(int c,int last)
{
int p=last,np=++tot;
if(tot>=2e6)
{
cout<<sum;
exit(0);
}
len[np]=len[p]+1;
while(p&&!tr[p][c])
{
tr[p][c]=np;
p=fa[p];
}
if(!p) fa[np]=1;
else
{
int q=tr[p][c];
if(len[q]==len[p]+1) fa[np]=q;
else
{
int nq=++tot;
copy(nq,q);
len[nq]=len[p]+1;
fa[np]=fa[q]=nq;
while(p&&tr[p][c]==q)
{
tr[p][c]=nq;
p=fa[p];
}
}
}
return np;
}
int trie[S][26];
char s[S];
vector<int> appear[S];
bool ins[S];
int length;
void Insert(int x)
{
int p=0;
ins[p]=1;
length=strlen(s+1);
for(int i=1;i<=length;i++)
{
int c=s[i]-'a';
if(!trie[p][c]) trie[p][c]=++tot;
p=trie[p][c];
if(x!=0) appear[p].push_back(x);
else ins[p]=1;
}
}
int num=0;
int lson[N*20],rson[N*20],rot[N];
int Max[N*20],Pos[N*20];
void pushup(int k)
{
if(Max[lson[k]]>Max[rson[k]])
{
Max[k]=Max[lson[k]];
Pos[k]=Pos[lson[k]];
}
else if(Max[lson[k]]<Max[rson[k]])
{
Max[k]=Max[rson[k]];
Pos[k]=Pos[rson[k]];
}
else
{
Max[k]=Max[lson[k]];
Pos[k]=Pos[lson[k]];
}
}
void Modify(int &k,int l,int r,int pos)
{
if(!k) k=++num;
if(l==r)
{
Max[k]++;
Pos[k]=l;
return;
}
int mid=(l+r)>>1;
if(pos<=mid) Modify(lson[k],l,mid,pos);
else Modify(rson[k],mid+1,r,pos);
pushup(k);
}
int Merge(int x,int y,int l,int r)
{
if(!x||!y) return x+y;
int k=++num;
if(l==r)
{
Max[k]=Max[x]+Max[y];
Pos[k]=l;
return k;
}
int mid=(l+r)>>1;
lson[k]=Merge(lson[x],lson[y],l,mid);
rson[k]=Merge(rson[x],rson[y],mid+1,r);
pushup(k);
return k;
}
void Better(PII &A,PII B)
{
if(Y(A)<Y(B)||(Y(A)==Y(B)&&X(A)>X(B))) A=B;
}
PII Ask(int k,int l,int r,int L,int R)
{
if(!k) return mk(0,0);
if(L<=l&&r<=R) return mk(Pos[k],Max[k]);
int mid=(l+r)>>1;
PII res=mk(0,0);
if(L<=mid) Better(res,Ask(lson[k],l,mid,L,R));
if(R>mid) Better(res,Ask(rson[k],mid+1,r,L,R));
return res;
}
struct edge
{
int y,next;
}e[N];
int link[N],t=0;
int pos[S],pre[S];
void add(int x,int y)
{
e[++t].y=y;
e[t].next=link[x];
link[x]=t;
}
queue<int> q;
int f[N][30];
int tt=0;
void dfs(int x)
{
for(int i=link[x];i;i=e[i].next)
{
int y=e[i].y;
f[y][0]=x;
for(int k=1;k<=tt;k++)
f[y][k]=f[f[y][k-1]][k-1];
dfs(y);
rot[x]=Merge(rot[x],rot[y],1,m);
}
}
void Build()
{
sum=tot;
tot=0;
nodes[0]=++tot;
q.push(0);
while(!q.empty())
{
int x=q.front();
q.pop();
for(int c=0;c<26;c++)
{
if(!trie[x][c]) continue;
int y=trie[x][c];
nodes[y]=Extend(c,nodes[x]);
for(int p=0;p<(int)appear[y].size();p++)
Modify(rot[nodes[y]],1,m,appear[y][p]);
if(ins[x]&&ins[y])
{
pre[y]=pre[x]+1;
pos[pre[y]]=nodes[y];
}
q.push(y);
}
}
for(int i=2;i<=tot;i++)
add(fa[i],i);
tt=log2(tot+1);
dfs(1);
}
inline int read()
{
int X=0;bool flag=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')flag=0;ch=getchar();}
while(ch>='0'&&ch<='9'){X=(X<<1)+(X<<3)+ch-'0';ch=getchar();}
if(flag) return X;
return ~(X-1);
}
int Find(int x,int l)
{
int r=x;
for(int k=tt;k>=0;k--)
{
if(f[x][k]&&len[r]-len[f[x][k]]<=l)
{
x=f[x][k];
}
}
return x;
}
int main()
{
scanf("%s",s+1);
Insert(0);
m=read();
for(int i=1;i<=m;i++)
{
scanf("%s",s+1);
Insert(i);
}
Build();
int q=read();
while(q--)
{
int l=read(),r=read(),ql=read(),qr=read();
ql--;
int x=Find(pos[qr],ql);
PII ans=Ask(rot[x],1,m,l,r);
if(X(ans)==0) X(ans)=l;
printf("%d %d\n",X(ans),Y(ans));
}
return 0;
}
CF235C Cyclical Quest
洛谷题目传送门
如果暴力把循环同构字符串建成广义SAM,显然空间时间都炸裂
因此考虑换一种思路
对原串S建出SAM,然后把询问串倍长,扔进SAM里跑
求出对于某一个前缀,可以和S匹配的最长后缀长度,设为\(len\)
那么如果询问串长度为\(m\)
只有\(len\geq m\)才是合法的
然后用树上倍增找到对应的区间
然后把答案加上这个区间在\(S\)中的出现次数(子树和)就行了
因为循环同构字符串可能会相同,产生了重复贡献,所以我们强制一个节点只能产生一次贡献
贡献过了标记就可以了
别忘了询问过后撤销标记
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e6+7;
int tr[N][26],fa[N],len[N];
int last=1,tot=1;
void copy(int x,int y)
{
len[x]=len[y];
fa[x]=fa[y];
for(int c=0;c<26;c++)
tr[x][c]=tr[y][c];
}
LL f[N];
void Extend(int c)
{
int p=last,np=last=++tot;
len[np]=len[p]+1;
f[np]=1;
while(p&&!tr[p][c])
{
tr[p][c]=np;
p=fa[p];
}
if(!p) fa[np]=1;
else
{
int q=tr[p][c];
if(len[q]==len[p]+1) fa[np]=q;
else
{
int nq=++tot;
copy(nq,q);
len[nq]=len[p]+1;
fa[np]=fa[q]=nq;
while(p&&tr[p][c]==q)
{
tr[p][c]=nq;
p=fa[p];
}
}
}
}
struct edge
{
int y,next;
}e[2*N];
int link[N],t=0;
void add(int x,int y)
{
e[++t].y=y;
e[t].next=link[x];
link[x]=t;
}
void dfs(int x)
{
for(int i=link[x];i;i=e[i].next)
{
int y=e[i].y;
dfs(y);
f[x]+=f[y];
}
}
char s[N];
int used[N],cnt=0;
bool tag[N];
int Find(int x,int m)
{
while(fa[x]&&len[fa[x]]>=m)
x=fa[x];
return x;
}
void solve()
{
scanf("%s",s+1);
int m=strlen(s+1);
for(int i=m+1;i<=m+m;i++)
s[i]=s[i-m];
int p=1;
LL ans=0;
int t=0;
for(int i=1;i<=m+m;i++)
{
int c=s[i]-'a';
if(tr[p][c]) p=tr[p][c],t++;
else
{
while(p&&!tr[p][c]) p=fa[p];
if(!p) p=1,t=0;
else
{
t=len[p]+1;
p=tr[p][c];
}
}
if(t<m) continue;
int q=Find(p,m);
if(tag[q]) continue;
ans+=f[q];
tag[q]=1;
used[++cnt]=q;
}
printf("%lld\n",ans);
for(int i=1;i<=cnt;i++)
tag[used[i]]=0;
cnt=0;
}
int main()
{
scanf("%s",s+1);
int m=strlen(s+1);
for(int i=1;i<=m;i++)
Extend(s[i]-'a');
for(int i=2;i<=tot;i++)
add(fa[i],i);
dfs(1);
int n;
scanf("%d",&n);
while(n--)
{
solve();
}
return 0;
}
[HEOI2016/TJOI2016]字符串
洛谷题目传送门
不理解为什么是黑色的
对S建出后缀自动机
询问的时候二分答案,找到\(s[c……c+mid-1]\)对应的字串的节点
然后查询这个节点在\(s[a……b]\)中出现了没
可以用线段树合并维护出现的位置的集合
复杂度\(O(n\log ^2n)\)
代码也很好写
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+7;
char s[N];
int n;
int tr[N][26],len[N],fa[N];
void copy(int a,int b)
{
len[a]=len[b];
fa[a]=fa[b];
for(int c=0;c<26;c++)
tr[a][c]=tr[b][c];
}
int last=1,tot=1;
int tree[N*20],lson[N*20],rson[N*20];
int cnt=0;
int rot[N];
void pushup(int k)
{
tree[k]=tree[lson[k]]+tree[rson[k]];
}
void Insert(int &k,int l,int r,int x)
{
if(!k) k=++cnt;
if(l==r)
{
tree[k]++;
return;
}
int mid=(l+r)>>1;
if(x<=mid) Insert(lson[k],l,mid,x);
else Insert(rson[k],mid+1,r,x);
pushup(k);
}
int Merge(int x,int y,int l,int r)
{
if(!x||!y) return x+y;
int k=++cnt;
tree[k]=tree[x]+tree[y];
if(l==r) return k;
int mid=(l+r)>>1;
lson[k]=Merge(lson[x],lson[y],l,mid);
rson[k]=Merge(rson[x],rson[y],mid+1,r);
return k;
}
struct edge
{
int y,next;
}e[2*N];
int link[N],t=0;
void add(int x,int y)
{
e[++t].y=y;
e[t].next=link[x];
link[x]=t;
}
int pos[N];
void Extend(int c,int x)
{
int p=last,np=last=++tot;
len[np]=len[p]+1;
pos[x]=np;
Insert(rot[np],1,n,x);
while(p&&!tr[p][c])
{
tr[p][c]=np;
p=fa[p];
}
if(!p) fa[np]=1;
else
{
int q=tr[p][c];
if(len[q]==len[p]+1) fa[np]=q;
else
{
int nq=++tot;
copy(nq,q);
fa[np]=fa[q]=nq;
len[nq]=len[p]+1;
while(p&&tr[p][c]==q)
{
tr[p][c]=nq;
p=fa[p];
}
}
}
}
int f[N][20];
void dfs(int x)
{
for(int i=link[x];i;i=e[i].next)
{
int y=e[i].y;
f[y][0]=x;
for(int k=1;k<=18&&f[y][k-1];k++)
f[y][k]=f[f[y][k-1]][k-1];
dfs(y);
rot[x]=Merge(rot[x],rot[y],1,n);
}
}
int Find(int x,int lenth)
{
for(int i=18;i>=0;i--)
if(f[x][i]&&len[f[x][i]]>=lenth) x=f[x][i];
return x;
}
int Pick(int k,int l,int r,int L,int R)
{
if(!k) return 0;
if(L<=l&&r<=R) return tree[k];
int mid=(l+r)>>1;
int res=0;
if(L<=mid) res=res+Pick(lson[k],l,mid,L,R);
if(R>mid) res=res+Pick(rson[k],mid+1,r,L,R);
return res;
}
bool check(int a,int b,int c,int lenth)
{
if(lenth==0) return 1;
int x=Find(pos[c+lenth-1],lenth);
// cout<<pos[c+lenth-1]<<' '<<x<<endl;
if(Pick(rot[x],1,n,a+lenth-1,b)>0) return 1;
return 0;
}
int calc(int a,int b,int c,int d)
{
int l=0,r=min(b-a+1,d-c+1),mid,ans=0;
while(l<=r)
{
mid=(l+r)>>1;
if(check(a,b,c,mid))
{
ans=mid;
l=mid+1;
}
else r=mid-1;
}
return ans;
}
int m;
void Put()
{
for(int i=1;i<=tot;i++)
for(int c=0;c<26;c++)
if(tr[i][c])
cout<<i<<' '<<tr[i][c]<<' '<<c<<endl;
}
void Pl()
{
for(int i=2;i<=tot;i++)
cout<<fa[i]<<' '<<i<<endl;
}
void Build()
{
cin>>n>>m;
scanf("%s",s+1);
for(int i=1;i<=n;i++)
Extend(s[i]-'a',i);
for(int i=2;i<=tot;i++)
add(fa[i],i);
// Put();
// Pl();
dfs(1);
}
int main()
{
Build();
while(m--)
{
int a,b,c,d;
scanf("%d %d %d %d",&a,&b,&c,&d);
printf("%d\n",calc(a,b,c,d));
}
return 0;
}
CF1037H Security
洛谷题目传送门
首先敏感的话就知道只是一道SAM的题
而且又有区间的限制,所以肯定需要用线段树合并维护\(endpos\)集合
然后考虑这道题
首先,肯定答案字符串的某一个前缀和\(T\)的某一个前缀是相同的,后面再接上一个字符,使得字符正好大于\(T\)之一位的字符
而且这个前缀的长度肯定越长越好
因此我们考虑倒叙枚举前缀长度
然后判断能否在\(SAM\)上走出这个字串
接着从小到大的枚举这一位填的字符,注意这个字符要比\(T\)在这一位的字符大
如果可以找到,那么直接就是答案了
否则缩短前缀长度,继续比较
用线段树合并维护是否在某个区间内出现某个字串就可以了
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+7;
struct edge
{
int y,next;
} e[2*N];
int link[N],t=0;
void add(int x,int y)
{
e[++t].y=y;
e[t].next=link[x];
link[x]=t;
}
int tr[N][26];
int len[N],fa[N];
void copy(int a,int b)
{
len[a]=len[b];
fa[a]=fa[b];
for(int c=0;c<26;c++)
tr[a][c]=tr[b][c];
}
int tot=1,last=1;
int tree[N*20],lson[N*20],rson[N*20],rot[N];
int cnt=0;
void pushup(int k)
{
tree[k]=tree[lson[k]]+tree[rson[k]];
}
void Insert(int &k,int l,int r,int x)
{
if(!k) k=++cnt;
if(l==r)
{
tree[k]++;
return;
}
int mid=(l+r)>>1;
if(x<=mid) Insert(lson[k],l,mid,x);
else Insert(rson[k],mid+1,r,x);
pushup(k);
}
int Merge(int x,int y,int l,int r)
{
if(!x||!y) return x+y;
int k=++cnt;
tree[k]=tree[x]+tree[y];
if(l==r) return k;
int mid=(l+r)>>1;
lson[k]=Merge(lson[x],lson[y],l,mid);
rson[k]=Merge(rson[x],rson[y],mid+1,r);
return k;
}
int n;
int B;
void Extend(int c,int x)
{
int p=last,np=last=++tot;
len[np]=len[p]+1;
Insert(rot[np],1,n,x);
while(p&&!tr[p][c])
{
tr[p][c]=np;
p=fa[p];
}
if(!p) fa[np]=1;
else
{
int q=tr[p][c];
if(len[q]==len[p]+1) fa[np]=q;
else
{
int nq=++tot;
copy(nq,q);
len[nq]=len[p]+1;
fa[np]=fa[q]=nq;
while(tr[p][c]==q)
{
tr[p][c]=nq;
p=fa[p];
}
}
}
}
char s[N];
int m;
void dfs(int x)
{
for(int i=link[x];i;i=e[i].next)
{
int y=e[i].y;
dfs(y);
rot[x]=Merge(rot[x],rot[y],1,n);
}
}
void Build()
{
n=strlen(s+1);
for(int i=1;i<=n;i++)
Extend(s[i]-'a',i);
for(int i=2;i<=tot;i++)
add(fa[i],i);
dfs(1);
B=n;
// for(int i=1;i<=tot;i++)
// for(int c=0;c<26;c++)
// if(tr[i][c])
// cout<<i<<' '<<tr[i][c]<<' '<<(char)(c+'a')<<endl;
// for(int i=2;i<=tot;i++)
// cout<<fa[i]<<' '<<i<<endl;
}
int Ask(int k,int l,int r,int L,int R)
{
// cout<<k<<endl;
if(!k) return 0;
if(L>R) return 0;
// cout<<"sum:"<<tree[k]<<' '<<l<<' '<<r<<endl;
if(L<=l&&r<=R) return tree[k];
int mid=(l+r)>>1;
int res=0;
if(L<=mid) res+=Ask(lson[k],l,mid,L,R);
if(R>mid) res+=Ask(rson[k],mid+1,r,L,R);
return res;
}
bool Road(int x,int l,int r,int c,int len)
{
if(!tr[x][c]) return 0;
// cout<<tr[x][c]<<"++"<<endl;
return Ask(rot[tr[x][c]],1,B,l+len-1,r)>0;
}
void Put(int x,int r)
{
if(x>r) return;
putchar((char)s[x]);
Put(x+1,r);
}
bool Pick(int l,int r,int x,int p)
{
// cout<<x<<"in"<<p<<endl;
if(x==n+1)
{
for(int ch=0;ch<26;ch++)
if(Road(p,l,r,ch,x))
{
Put(1,x-1);
putchar((char)ch+'a');
putchar('\n');
return 1;
}
// cout<<"==="<<endl;
return 0;
}
int c=s[x]-'a';
if(Road(p,l,r,c,x)&&Pick(l,r,x+1,tr[p][c])) return 1;
for(int ch=c+1;ch<26;ch++)
if(Road(p,l,r,ch,x))
{
Put(1,x-1);
putchar((char)ch+'a');
putchar('\n');
return 1;
}
return 0;
}
int main()
{
scanf("%s",s+1);
Build();
scanf("%d",&m);
while(m--)
{
int l,r;
scanf("%d %d %s",&l,&r,s+1);
n=strlen(s+1);
bool flag=Pick(l,r,1,1);
if(!flag) printf("-1\n");
}
return 0;
}
CF700E Cool Slogans
洛谷题目传送门
首先显然对于第\(i\)个串,\(s_{i-1}\)一定是\(s_i\)的后缀
因为如果不是后缀,那么把最后的几个字符删了答案会更优
所以我们考虑建出\(SAM\)
那么答案的序列一定是\(parent\)树上一条从根到某个节点的路径上选取的,也就是不断跳后缀
那么基于贪心的策略,我们是能选就选
也就是判断这个点到根的路径上最后一个选的节点,判断这个节点是否在当前节点中出现了至少两次
如果出现了,则当前节点可以选
否则当前节点不能选
至于如何判断一个节点是否在另一个节点中出现至少两次
那么因为有后缀关系,所以在当前节点出现的位置,祖先节点也出现了
我们考虑求出任意一个当前节点出现的位置,设为\(R\)
如果当前节点是x,祖先节点是y
那么只要y在\([R-len_x+len_y,R-1]\)中出现就可以了,因为可以重叠
用线段树合并维护就可以了
#include<bits/stdc++.h>
using namespace std;
const int N = 4e5+7;
struct edge
{
int y,next;
} e[2*N];
int link[N],t=0;
void add(int x,int y)
{
e[++t].y=y;
e[t].next=link[x];
link[x]=t;
}
int tr[N][26];
int len[N],fa[N];
void copy(int a,int b)
{
len[a]=len[b];
fa[a]=fa[b];
for(int c=0;c<26;c++)
tr[a][c]=tr[b][c];
}
int tot=1,last=1;
int tree[N*20],lson[N*20],rson[N*20],rot[N];
int cnt=0;
void pushup(int k)
{
tree[k]=tree[lson[k]]+tree[rson[k]];
}
void Insert(int &k,int l,int r,int x)
{
if(!k) k=++cnt;
if(l==r)
{
tree[k]++;
return;
}
int mid=(l+r)>>1;
if(x<=mid) Insert(lson[k],l,mid,x);
else Insert(rson[k],mid+1,r,x);
pushup(k);
}
int Merge(int x,int y,int l,int r)
{
if(!x||!y) return x+y;
int k=++cnt;
tree[k]=tree[x]+tree[y];
if(l==r) return k;
int mid=(l+r)>>1;
lson[k]=Merge(lson[x],lson[y],l,mid);
rson[k]=Merge(rson[x],rson[y],mid+1,r);
return k;
}
int n;
int B;
void Extend(int c,int x)
{
int p=last,np=last=++tot;
len[np]=len[p]+1;
Insert(rot[np],1,n,x);
while(p&&!tr[p][c])
{
tr[p][c]=np;
p=fa[p];
}
if(!p) fa[np]=1;
else
{
int q=tr[p][c];
if(len[q]==len[p]+1) fa[np]=q;
else
{
int nq=++tot;
copy(nq,q);
len[nq]=len[p]+1;
fa[np]=fa[q]=nq;
while(tr[p][c]==q)
{
tr[p][c]=nq;
p=fa[p];
}
}
}
}
char s[N];
void dfs(int x)
{
for(int i=link[x];i;i=e[i].next)
{
int y=e[i].y;
dfs(y);
rot[x]=Merge(rot[x],rot[y],1,n);
}
}
void Build()
{
n=strlen(s+1);
for(int i=1;i<=n;i++)
Extend(s[i]-'a',i);
for(int i=2;i<=tot;i++)
add(fa[i],i);
// for(int i=1;i<=tot;i++)
// for(int c=0;c<26;c++)
// if(tr[i][c]) cout<<i<<' '<<tr[i][c]<<' '<<(char)(c+'a')<<endl;
dfs(1);
}
int Ask(int k,int l,int r,int L,int R)
{
if(!k) return 0;
if(L>R) return 0;
if(L<=l&&r<=R) return tree[k];
int mid=(l+r)>>1;
int res=0;
if(L<=mid) res+=Ask(lson[k],l,mid,L,R);
if(R>mid) res+=Ask(rson[k],mid+1,r,L,R);
return res;
}
int Get(int k,int l,int r)
{
if(l==r) return l;
int mid=(l+r)>>1;
if(tree[lson[k]]) return Get(lson[k],l,mid);
return Get(rson[k],mid+1,r);
}
bool Twice(int x,int y)
{
if(y==1) return 1;
int pos=Get(rot[x],1,n);
return Ask(rot[y],1,n,pos-len[x]+len[y],pos-1)>0;
}
int ans=0;
int f[N],g[N];
void dp(int x)
{
ans=max(ans,f[x]);
for(int i=link[x];i;i=e[i].next)
{
int y=e[i].y;
if(Twice(y,g[x]))
{
f[y]=f[x]+1;
g[y]=y;
}
else f[y]=f[x],g[y]=g[x];
dp(y);
}
}
int main()
{
cin>>n;
scanf("%s",s+1);
Build();
// cout<<n;
f[1]=0;
g[1]=1;
dp(1);
cout<<ans;
return 0;
}
[CTSC2010]珠宝商
洛谷题目传送门
简要题意
给定一颗\(n\)个节点的树,和一个长度为\(m\)的模式串\(S\)
树上每个节点都有一个字符
求树上所有路径的点的字符拼成的字符串在\(S\)中的出现次数之和
[CTSC]珠宝商题解
[CTSC2012]熟悉的文章
洛谷题目传送门
首先二分答案
然后用\(dp\)求出答案
设\(f[i]\)表示以\(i\)为最后一段结尾的最大权值
考虑如何转移
\(I:\)当前字串是熟悉的的字串
枚举上一个划分位置j则
其中需要满足条件
其中\(match[i]\)表示以\(i\)结尾的前缀和文本串匹配的最长后缀长度
这个东西可以把文本串建出广义SAM然后跑匹配得到
\(II:\)当前子串不是熟悉的字串
根据贪心,这一段的长度越小越好
所以
两个总和起来就是答案
观察到一个中\(i-L,i-match[i]\)都是单调递增的
因此我们考虑用单调队列维护\(f[j]-j\)的最大值就能转移了
#include<bits/stdc++.h>
using namespace std;
const int N = 2e6+7;
int n,m;
int trie[N][2];
int tr[2*N][2],fa[2*N],len[2*N];
char s[N];
int cnt=0;
void Insert()
{
int t=strlen(s+1);
int p=0;
for(int i=1;i<=t;i++)
{
int c=s[i]-'0';
if(!trie[p][c]) trie[p][c]=++cnt;
p=trie[p][c];
}
}
queue<int> q;
int tot=1;
int Id[N];
void copy(int a,int b)
{
fa[a]=fa[b];
len[a]=len[b];
for(int c=0;c<2;c++)
tr[a][c]=tr[b][c];
}
int Extend(int c,int last)
{
int p=last,np=++tot;
len[np]=len[p]+1;
while(p&&!tr[p][c])
{
tr[p][c]=np;
p=fa[p];
}
if(!p) fa[np]=1;
else
{
int q=tr[p][c];
if(len[q]==len[p]+1) fa[np]=q;
else
{
int nq=++tot;
copy(nq,q);
len[nq]=len[p]+1;
fa[np]=fa[q]=nq;
while(p&&tr[p][c]==q)
{
tr[p][c]=nq;
p=fa[p];
}
}
}
return np;
}
void Build()
{
Id[0]=1;
q.push(0);
while(!q.empty())
{
int x=q.front();
q.pop();
for(int c=0;c<2;c++)
{
int y=trie[x][c];
if(!y) continue;
Id[y]=Extend(c,Id[x]);
q.push(y);
}
}
// for(int i=1;i<=tot;i++)
// for(int c=0;c<2;c++)
// if(tr[i][c]) cout<<i<<' '<<tr[i][c]<<' '<<c<<endl;
}
int match[N];
void getmatch()
{
int t=0,p=1;
for(int i=1;i<=m;i++)
{
int c=s[i]-'0';
while(p&&!tr[p][c]) p=fa[p],t=len[p];
if(!p) p=1,t=0;
else
{
t++;
p=tr[p][c];
}
match[i]=t;
// cout<<i<<' '<<match[i]<<endl;
}
}
deque<int> Q;
int f[N];
bool check(int L)
{
Q.push_back(0);
f[0]=0;
for(int i=1;i<=L-1;i++) f[i]=0;
for(int i=L;i<=m;i++)
{
f[i]=f[i-1];
while(!Q.empty()&&Q.front()<i-match[i]) Q.pop_front();
if(!Q.empty())
{
int j=Q.front();
f[i]=max(f[i],f[j]+i-j);
}
while(!Q.empty()&&f[Q.back()]-Q.back()<=f[i-L+1]-(i-L+1)) Q.pop_back();
Q.push_back(i-L+1);
}
while(!Q.empty()) Q.pop_back();
if(f[m]*10>=m*9) return 1;
return 0;
}
int search()
{
int l=1,r=m,mid,ans=0;
while(l<=r)
{
mid=(l+r)>>1;
if(check(mid))
{
ans=mid;
l=mid+1;
}
else r=mid-1;
}
return ans;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
scanf("%s",s+1);
Insert();
}
Build();
while(n--)
{
scanf("%s",s+1);
m=strlen(s+1);
getmatch();
printf("%d\n",search());
}
return 0;
}

浙公网安备 33010602011771号