字符串专项测试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;
}
posted @ 2021-12-12 20:05  keen_z  阅读(37)  评论(0)    收藏  举报