cf963d D. Frequency of String

链接

点击跳转

前置知识

首先要知道一个事情:所有查询串出现位置的总和不会超过 O ( n n ) O(n \sqrt n) O(nn )

因为长度相同的答案之和不会超过 O ( n ) O(n) O(n),而最多有 O ( n ) O(\sqrt n) O(n )种不同的长度

所以如果有一种做法是把所有的位置都给找出来,那么这种方法是可行的

做法1

ac自动机的暴力做法就是把查询串建出来自动机然后跑

但是这样有很多无用跳转,我直接把每个点能跳到的第一个有用的点记下来,那么这样每次操作都对答案有贡献

而答案的总规模已经说过了是 O ( n n ) O(n \sqrt n) O(nn ),所以这种做法复杂度也是 O ( n n ) O(n \sqrt n) O(nn )

代码(做法2)

显然 s a m sam sam具有的天然的性质就是每个点的 e n d p o s endpos endpos都显示了这组串在哪些位置出现了

我只要能把这个信息给提取出来,而且在复杂度上不要有浪费,那么也可以保证算法的复杂度和答案同规模

显然我们需要一个排好序的 e n d p o s endpos endpos序列

一个很老的套路是 p a r e n t parent parent树上跑线段树合并,但是那样肯定会带一个 l o g log log O ( n n log ⁡ n ) O(n \sqrt n \log n) O(nn logn)看起来很不好

其实,要求的就是在自动机上到的那个点 p p p e n d p o s endpos endpos集合,而且我们需要把这个集合有序化才能做后续操作,这个集合不能对每个点直接求(空间规模会达到 O ( n 2 ) O(n^2) O(n2)),而线段树合并在查询的时候又会带上一个 l o g log log

平衡树看起来不错(启发式合并即可),但是太难写

有一个解决方案是一边 d f s dfs dfs一边归并,我没写,但是感觉可能是对的

有一个性质:你所要求的就是一个子树上所有带有前缀的点的信息形成的有序序列

那么说到子树就是 d f s dfs dfs序了,所以现在只需要把一个区间里的有效信息提取出来并有序化即可

通过离散化,或者维护 n e x t a c t i v e next_active nextactive,都可以把信息提取出来,但是问题又来了,我不能排序,怎么有序化?

我这里用了一个类似桶排序的东西, a c c [ i ] acc[i] acc[i]是一个 v e c t o r vector vector,按顺序存储了“哪些询问”的答案包含这个点

最后再扫一遍就行了

#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#define iinf 0x3f3f3f3f
#define linf (1ll<<60)
#define eps 1e-8
#define maxn 100010
#define maxe 200010
#define cl(x) memset(x,0,sizeof(x))
#define rep(i,a,b) for(i=a;i<=b;i++)
#define drep(i,a,b) for(i=a;i>=b;i--)
#define em(x) emplace(x)
#define emb(x) emplace_back(x)
#define emf(x) emplace_front(x)
#define fi first
#define se second
#define de(x) cerr<<#x<<" = "<<x<<endl
using namespace std;
using namespace __gnu_pbds;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
ll read(ll x=0)
{
    ll c, f(1);
    for(c=getchar();!isdigit(c);c=getchar())if(c=='-')f=-f;
    for(;isdigit(c);c=getchar())x=x*10+c-0x30;
    return f*x;
}
struct SAM
{
    int tot, las, ch[maxn<<1][26], fa[maxn<<1], len[maxn<<1], pref[maxn<<1];
    int* operator[](int u){return ch[u];}
    void init()
    {
        int i;
        rep(i,1,tot)cl(ch[i]),fa[i]=len[i]=pref[i]=0;
        tot=las=1;
    }
    void append(int c, int tag=1)
    {
        int p(las);
        len[las=++tot]=len[p]+1;
        pref[las]=tag;
        for(;p and !ch[p][c];p=fa[p])ch[p][c]=las;
        if(!p)fa[las]=1;
        else
        {
            int q=ch[p][c];
            if(len[q]==len[p]+1)fa[las]=q;
            else
            {
                int qq=++tot;
                memcpy(ch[qq],ch[q],sizeof(ch[q]));
                fa[qq]=fa[q];
                len[qq]=len[p]+1;
                fa[q]=fa[las]=qq;
                for(;ch[p][c]==q;p=fa[p])ch[p][c]=qq;
            }
        }
    }
}sam;
struct Graph
{
    int etot, head[maxn<<1], to[maxe], next[maxe], w[maxe];
    void clear(int N)
    {
        for(int i=1;i<=N;i++)head[i]=0;
        etot=0;
    }
    void adde(int a, int b, int c=0){to[++etot]=b;w[etot]=c;next[etot]=head[a];head[a]=etot;}
    #define forp(_,__) for(auto p=__.head[_];p;p=__.next[p])
}G;
struct Easy_Tree
{
    int depth[maxn<<1], dist[maxn<<1], tid[maxn<<1], rtid[maxn<<1], tim, size[maxn<<1], rev[maxn<<1];
    void dfs(int pos, int pre, Graph& G)
    {
        tid[pos]=++tim;
        rev[tid[pos]]=pos;
        size[pos]=1;
        forp(pos,G)if(G.to[p]!=pre)
        {
            depth[G.to[p]]=depth[pos]+1;
            dist[G.to[p]]=dist[pos]+G.w[p];
            dfs(G.to[p],pos,G);
            size[pos]+=size[G.to[p]];
        }
        rtid[pos]=tim;
    }
    void run(Graph& G, int root)
    {
        tim=0;
        depth[root]=1;
        dfs(1,0,G);
    }
}et;
char s[maxn], t[maxn];
int m, k[maxn], n, next_active[maxn<<1], len[maxn];
vector<int> acc[maxn], v[maxn];
int calc(vector<int>& v, int k, int len)
{
    int i, j, ans = iinf;
    if(v.size()<k)return -1;
    j=0;
    rep(i,0,v.size()-1)
    {
        while(j<v.size() and j-i+1<k)j++;
        if(j<v.size())
            ans = min(ans,v[j]-v[i]+len);
    }
    return ans;
}
int main()
{
    int i, kase;
    scanf("%s%d",s+1,&m);
    n = strlen(s+1);
    sam.init();
    rep(i,1,n)sam.append(s[i]-'a', i);
    rep(i,2,sam.tot)G.adde(sam.fa[i],i);
    et.run(G,1);
    next_active[sam.tot+1]=iinf;
    drep(i,sam.tot,0)
        if(sam.pref[et.rev[i+1]])next_active[i]=i+1;
        else next_active[i]=next_active[i+1];
    rep(kase,1,m)
    {
        scanf("%d%s",k+kase,t+1);
        len[kase] = strlen(t+1);
        int p = 1;
        rep(i,1,len[kase])p=sam[p][t[i]-'a'];
        if(p==0)continue;
        for(auto pp = next_active[ et.tid[p] - 1 ]; pp<=et.rtid[p]; pp=next_active[pp])
            acc[sam.pref[et.rev[pp]]].emb(kase);
    }
    rep(i,1,n)for(auto x:acc[i])v[x].emb(i);
    rep(kase,1,m)printf("%d\n",calc(v[kase],k[kase],len[kase]));
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值