字符串专项测试1
T1 回文子串
考场上一眼感觉是个分块,然后开码了两小时,结果越想越乱。。还因为块分得过于狂野T飞了。。
膜bai分块爆切%%%
正解是用线段树维护以每个点为开始的回文串个数和字符序列。
每次修改,区间中部的回文串个数都会变成 \(k\) ,然后暴力 \(manacher\) 修改 \([l-k+1,l]\) 和 \([r-k+2,r]\) 的个数。回文串个数可以差分算。
查询同理,中部直接线段树查,边角跑 \(manacher\) 。
注意记的回文串个数必须合法,即长度不能大于 \(k\) ,因此差分时要把回文半径和 \(k\) 取 \(\min\) 。这里调了好久。。
其实思想和分块是一样的。
\(code:\)
T1
#include<bits/stdc++.h>
using namespace std;
namespace IO{
typedef long long LL;
int read(){
int x=0,f=0; char ch=getchar();
while(ch<'0'||ch>'9'){ f|=(ch=='-'); ch=getchar(); }
while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
return f?-x:x;
} char output[50];
void write(LL x,char sp){
int len=0;
if(x<0) x=-x, putchar('-');
do{ output[len++]=x%10+'0'; x/=10; }while(x);
for(int i=len-1;~i;i--) putchar(output[i]); putchar(sp);
}
void ckmin(int &x,int y){ x=x<y?x:y; }
void ckmax(int &x,int y){ x=x>y?x:y; }
} using namespace IO;
const int NN=50010;
int n,k,q;
char c,s[NN];
namespace Segment_Tree{
#define ld rt<<1
#define rd (rt<<1)|1
const int TN=NN<<2;
int len[TN],sum[TN],let[TN],tag[TN],gat[TN];
void pushup(int rt){ sum[rt]=sum[ld]+sum[rd]; }
void down(int rt,int val){ let[rt]=tag[rt]=val; }
void push(int rt,int val){ gat[rt]=val; sum[rt]=val*len[rt]; }
void pushdown(int rt){
if(tag[rt]!=-1){ down(ld,tag[rt]); down(rd,tag[rt]); }
if(gat[rt]!=-1){ push(ld,gat[rt]); push(rd,gat[rt]); }
tag[rt]=gat[rt]=-1;
}
void build(int rt,int l,int r){
tag[rt]=gat[rt]=-1; len[rt]=r-l+1;
if(l==r){ let[rt]=s[l]-'a'; return; }
int mid=l+r>>1;
build(ld,l,mid); build(rd,mid+1,r);
}
void update(int opl,int opr,int val,int rt=1,int l=1,int r=n){
if(opl>opr) return;
if(l>=opl&&r<=opr) return push(rt,val),void();
pushdown(rt);
int mid=l+r>>1;
if(opl<=mid) update(opl,opr,val,ld,l,mid);
if(opr>mid) update(opl,opr,val,rd,mid+1,r);
pushup(rt);
}
void modify(int opl,int opr,int val,int rt=1,int l=1,int r=n){
if(opl>opr) return;
if(l>=opl&&r<=opr) return down(rt,val),void();
pushdown(rt);
int mid=l+r>>1;
if(opl<=mid) modify(opl,opr,val,ld,l,mid);
if(opr>mid) modify(opl,opr,val,rd,mid+1,r);
}
int query(int opl,int opr,int rt=1,int l=1,int r=n){
if(opl>opr) return 0;
if(l>=opl&&r<=opr) return sum[rt];
pushdown(rt);
int mid=l+r>>1,res=0;
if(opl<=mid) res+=query(opl,opr,ld,l,mid);
if(opr>mid) res+=query(opl,opr,rd,mid+1,r);
return res;
}
int look(int pos,int rt=1,int l=1,int r=n){
if(l==r) return let[rt];
pushdown(rt);
int mid=l+r>>1;
if(pos<=mid) return look(pos,ld,l,mid);
else return look(pos,rd,mid+1,r);
}
#undef ld
#undef rd
} using namespace Segment_Tree;
namespace Manacher{
int m,p[NN<<1],num[NN<<1];
char ch[NN<<1];
void manacher(int ll,int rr){
ch[m=0]='~'; ch[++m]='#';
for(int i=ll;i<=rr;i++)
ch[++m]=look(i)+'a', ch[++m]='#';
ch[++m]='!';
for(int i=1;i<=m;i++) num[i]=0;
int mid,r=0;
for(int i=1;i<=m;i++){
if(r>i) p[i]=min(p[(mid<<1)-i],r-i);
else p[i]=1;
while(i-p[i]>1&&i+p[i]<m&&ch[i-p[i]]==ch[i+p[i]]) ++p[i];
if(i+p[i]>r) r=i+p[i], mid=i;
++num[i-min(k,p[i])+1];
--num[i+1];
}
for(int i=1;i<=m;i++)
num[i]+=num[i-1], p[i]=0;
}
} using namespace Manacher;
signed main(){
scanf("%s",s+1); n=strlen(s+1);
k=read(); q=read();
build(1,1,n); manacher(1,n);
for(int i=1;i<=n;i++) update(i,i,min(k,num[i<<1]));
while(q--){
int op=read(),l=read(),r=read();
if(op==1){
cin>>c;
update(l,r-k+1,k); modify(l,r,c-'a');
manacher(max(1,l-k+1),min(n,l+k));
for(int i=2;i<m;i+=2){
int pos=max(0,l-k)+(i>>1);
if(pos>l) break;
update(pos,pos,min(k,num[i]));
}
manacher(max(1,r-k+2),min(n,r+k));
for(int i=2;i<m;i+=2){
int pos=max(0,r-k+1)+(i>>1);
if(pos>r) break;
update(pos,pos,min(k,num[i]));
}
} else{
int res=query(l,r-k);
manacher(max(l,r-k+1),r);
for(int i=2;i<m;i+=2) res+=num[i];
write(res,'\n');
}
}
return 0;
}
T2 recollection
既有 \(LCP\) 又有 \(LCS\) ,那么串的正反肯定不重要了。把输入的树变成外向树后就得到了一颗 \(trie\) 。
在 \(trie\) 上建出广义 \(SAM\) 后得到 \(parent\) 树,现在 \(LCP\) 和 \(LCS\) 在两棵树中都已有了意义: \(LCP\) 为两个节点在 \(trie\) 上 \(LCA\) 的深度, \(LCS\) 为两个节点在 \(parent\) 树上 \(LCA\) 的最大长度。 然后就不会了
然后考虑如何在合法复杂度内把两个树形结构的信息合并。然后有了我不会的结论:
一棵树上任意 \(x\) 个节点的 \(LCA\) 集合等价于这 \(x\) 各节点中 \(DFS\) 序相邻节点的 \(LCA\) 集合。、
于是用某个数据结构维护 \(parent\) 树中每个子树在 \(trie\) 上 \(DFS\) 序的集合,然后在 \(parent\) 树上 \(DFS\) 进行合并即可。
可以线段树合并或者用 set 启发式合并。
\(code:\)
T2
#include<bits/stdc++.h>
using namespace std;
namespace IO{
typedef long long LL;
int read(){
int x=0,f=0; char ch=getchar();
while(ch<'0'||ch>'9'){ f|=(ch=='-'); ch=getchar(); }
while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
return f?-x:x;
} char output[50];
void write(LL x,char sp){
int len=0;
if(x<0) x=-x, putchar('-');
do{ output[len++]=x%10+'0'; x/=10; }while(x);
for(int i=len-1;~i;i--) putchar(output[i]); putchar(sp);
}
void ckmin(int &x,int y){ x=x<y?x:y; }
void ckmax(int &x,int y){ x=x>y?x:y; }
} using namespace IO;
const int NN=400010;
int n,ans;
map<int,int>te[NN];
namespace G_SAM{
int tot,len[NN],link[NN];
map<int,int>se[NN];
int clone(int u,int l){
int now=++tot;
len[now]=l; link[now]=link[u];
for(auto e:se[u])
if(len[e.second]) se[now][e.first]=e.second;
return now;
}
int extend(int pre,int c){
int now=se[pre][c],p=link[pre];
if(len[now]) return now;
len[now]=len[pre]+1;
while(~p&&!se[p].count(c))
se[p][c]=now, p=link[p];
if(p==-1){ link[now]=1; return now; }
int q=se[p][c];
if(len[q]==len[p]+1){ link[now]=q; return now; }
int cln=clone(q,len[p]+1);
while(~p&&se[p].count(c)&&se[p][c]==q)
se[p][c]=cln, p=link[p];
link[q]=link[now]=cln;
return now;
}
void build_SAM(){
tot=n; link[1]=-1;
for(int i=1;i<=n;i++) se[i]=te[i];
queue<pair<int,int>>q;
for(auto e:se[1]) q.push({1,e.first});
while(!q.empty()){
auto x=q.front(); q.pop();
int fa=extend(x.first,x.second);
for(auto e:se[fa]) q.push({fa,e.first});
}
}
} using namespace G_SAM;
struct Tree{
vector<int>e[NN];
void add(int a,int b){ e[a].push_back(b); }
}T,S;
namespace Tree_Chain{
int cnt,fa[NN],siz[NN],son[NN],top[NN],dfn[NN],pos[NN],dep[NN],mxd[NN];
void dfs1(int s,int f){
fa[s]=f; siz[s]=1;
for(int v:S.e[s]){
dep[v]=dep[s]+1;
dfs1(v,s);
siz[s]+=siz[v];
if(siz[v]>siz[son[s]]) son[s]=v;
}
}
void dfs2(int s,int t){
dfn[s]=++cnt; pos[cnt]=s; top[s]=t;
if(!son[s]) return;
dfs2(son[s],t);
for(int v:S.e[s]) if(v!=son[s]) dfs2(v,v);
}
int LCA(int x,int y){
while(top[x]!=top[y])
dep[top[x]]>dep[top[y]]?x=fa[top[x]]:y=fa[top[y]];
return dep[x]<dep[y]?x:y;
}
} using namespace Tree_Chain;
set<int>t[NN];
void dfs_T(int u){
if(u<=n) t[u].insert(dfn[u]);
for(int v:T.e[u]){
dfs_T(v); ckmax(mxd[u],mxd[v]);
if(t[u].size()<t[v].size()) swap(t[u],t[v]);
for(int x:t[v]){
t[u].insert(x);
int pre=(t[u].find(x)==t[u].begin())?-1:(*--t[u].find(x));
int nex=(t[u].find(x)==--t[u].end())?-1:(*++t[u].find(x));
if(~pre) ckmax(mxd[u],dep[LCA(pos[pre],pos[x])]);
if(~nex) ckmax(mxd[u],dep[LCA(pos[nex],pos[x])]);
}
t[v].clear();
ckmax(ans,len[u]+mxd[u]);
}
}
signed main(){
n=read();
for(int a,b,i=2;i<=n;i++){
a=read(); b=read();
te[a][b]=i;
}
build_SAM();
for(int i=1;i<=n;i++)
for(auto x:te[i]) T.add(i,x.second);
for(int i=1;i<=tot;i++)
if(~link[i]) S.add(link[i],i);
dfs1(1,0); dfs2(1,1); dfs_T(1);
write(ans,'\n');
return 0;
}
T3 回忆树
做法好像挺多的,但我只会离线QAQ
考虑把可能有贡献的路径分段,可以得到一条向上的祖先链,中间过 \(LCA\) 的一段和一条向下的祖先链。
中间过 \(LCA\) 的一段长度只有 \(2|S|\) ,可以直接拎出来 \(KMP\) 。对于两条链上的贡献,发现它们有可减性,即可以通过从根到链两个端点之间路径的贡献相减得到。
所以可以对询问串的正反串建出 \(AC\) 自动机,把询问离线,记录链上两个端点对应的贡献( \(1\) 或 \(-1\) ),并记下它们在 \(AC\) 自动机上对应的节点,然后在树上 \(DFS\) ,同时在 \(AC\) 自动机上跑,计算 \(AC\) 自动机 \(fail\) 树上子树和就好了。这个可以在 \(DFS\) 序上开树状数组实现。
个人认为挺神仙的。
\(code:\)
T3
#include<bits/stdc++.h>
using namespace std;
namespace IO{
typedef long long LL;
int read(){
int x=0,f=0; char ch=getchar();
while(ch<'0'||ch>'9'){ f|=(ch=='-'); ch=getchar(); }
while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
return f?-x:x;
} char output[50];
void write(LL x,char sp){
int len=0;
if(x<0) x=-x, putchar('-');
do{ output[len++]=x%10+'0'; x/=10; }while(x);
for(int i=len-1;~i;i--) putchar(output[i]); putchar(sp);
}
void ckmin(int &x,int y){ x=x<y?x:y; }
void ckmax(int &x,int y){ x=x>y?x:y; }
} using namespace IO;
const int NN=300010;
int n,m,len,ql[NN],qr[NN],let[NN],ans[NN];
int idx,w[NN<<1],to[NN<<1],nex[NN<<1],head[NN];
char c,s[NN];
struct ques{ int id,ps,vl; };
vector<ques>vec[NN];
void add(int a,int b,int c){
to[++idx]=b; nex[idx]=head[a]; head[a]=idx; w[idx]=c;
to[++idx]=a; nex[idx]=head[b]; head[b]=idx; w[idx]=c;
}
namespace AC_Automaton{
int tot,cnt,dfn[NN],siz[NN],fail[NN],tr[NN][26];
vector<int>e[NN];
int insert(char *ch,int id){
int l=strlen(ch+1),u=0;
for(int i=1;i<=l;i++){
int c=ch[i]-'a';
if(!tr[u][c]) tr[u][c]=++tot;
u=tr[u][c];
}
return u;
}
void build_fail(){
queue<int>q;
for(int i=0;i<26;i++) if(tr[0][i]) q.push(tr[0][i]);
while(!q.empty()){
int x=q.front(); q.pop();
for(int i=0;i<26;i++)
if(!tr[x][i]) tr[x][i]=tr[fail[x]][i];
else q.push(tr[x][i]), fail[tr[x][i]]=tr[fail[x]][i];
}
for(int i=1;i<=tot;i++) e[fail[i]].push_back(i);
}
void AC_dfs(int s){
dfn[s]=++cnt; siz[s]=1;
for(int v:e[s])
AC_dfs(v), siz[s]+=siz[v];
}
} using namespace AC_Automaton;
namespace GET_LCA{
int dep[NN],fa[NN][21];
void in_dfs(int s,int f){
fa[s][0]=f; dep[s]=dep[f]+1;
for(int i=1;i<=20;i++) fa[s][i]=fa[fa[s][i-1]][i-1];
for(int i=head[s];i;i=nex[i]) if(to[i]!=f){
let[to[i]]=w[i];
in_dfs(to[i],s);
siz[s]+=siz[to[i]];
}
}
int LCA(int x,int y){
if(dep[x]>dep[y]) swap(x,y);
for(int i=20;~i;i--)
if(dep[fa[y][i]]>=dep[x]) y=fa[y][i];
if(x==y) return x;
for(int i=20;~i;i--)
if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
int jump(int u,int l){
if(l<0) return u;
for(int i=20;~i;i--)
if(l&(1<<i)) u=fa[u][i];
return u;
}
} using namespace GET_LCA;
namespace KMP{
int lch,top,nxt[NN],stk[NN];
char ch[NN];
void get_string(int u,int v,int lca){
lch=0;
while(u&&u!=lca) ch[++lch]=let[u]+'a',u=fa[u][0];
while(v&&v!=lca) stk[++top]=let[v]+'a',v=fa[v][0];
while(top) ch[++lch]=stk[top--];
}
void get_nxt(){
nxt[0]=nxt[1]=0;
for(int j=0,i=2;i<=len;i++){
while(j&&s[i]!=s[j+1]) j=nxt[j];
if(s[i]==s[j+1]) ++j;
nxt[i]=j;
}
}
int match(int res=0){
if(lch<len) return 0;
for(int j=0,i=1;i<=lch;i++){
while(j&&ch[i]!=s[j+1]) j=nxt[j];
if(ch[i]==s[j+1]) ++j;
if(j==len) ++res, j=nxt[j];
}
return res;
}
} using namespace KMP;
namespace BIT{
int C[NN];
void insert(int pos,int val){ while(pos<=cnt){ C[pos]+=val; pos+=pos&(-pos); } }
int query(int pos,int res=0){ while(pos){ res+=C[pos]; pos-=pos&(-pos); } return res; }
int calc(int u){ return query(dfn[u]+siz[u]-1)-query(dfn[u]-1); }
} using namespace BIT;
void get_ready(int u,int v,int id){
int posv=insert(s,id);
reverse(s+1,s+len+1);
int posu=insert(s,id);
reverse(s+1,s+len+1);
int lca=LCA(u,v);
int locu=jump(u,dep[u]-dep[lca]-len+1);
int locv=jump(v,dep[v]-dep[lca]-len+1);
if(dep[u]-dep[lca]>=len){
vec[locu].push_back((ques){id,posu,-1});
vec[u].push_back((ques){id,posu,1});
}
if(dep[v]-dep[lca]>=len){
vec[locv].push_back((ques){id,posv,-1});
vec[v].push_back((ques){id,posv,1});
}
get_string(locu,locv,lca);
get_nxt(); ans[id]=match();
}
void dfs(int s,int f,int u){
insert(dfn[u],1);
for(ques x:vec[s]) ans[x.id]+=x.vl*calc(x.ps);
for(int i=head[s];i;i=nex[i])
if(to[i]!=f) dfs(to[i],s,tr[u][w[i]]);
insert(dfn[u],-1);
}
signed main(){
n=read(); m=read();
for(int a,b,i=1;i<n;i++){
a=read(); b=read(); cin>>c;
add(a,b,c-'a');
}
in_dfs(1,0);
for(int i=1;i<=m;i++){
int u=read(),v=read();
scanf("%s",s+1); len=strlen(s+1);
get_ready(u,v,i);
}
build_fail(); AC_dfs(0); dfs(1,0,0);
for(int i=1;i<=m;i++) write(ans[i],'\n');
return 0;
}

浙公网安备 33010602011771号