Frequency-of-String-cf963

题目

题目链接

内容

给出字符串s, ∣ s ∣ ≤ 1 e 5 |s|\le 1e^5 s1e5,有n个询问,每个询问给出字符串 m i m_i mi,和整数 k i k_i ki(每个字符串都不相同),求s的一个子串,字符串 m i m_i mi出现了 k i k_i ki次,求子串的最短长度,如果不存在这样的子串输出-1.

∑ 1 n ∣ m i ∣ ≤ 1 e 5 \sum_1^n |m_i|\le1e^5 1nmi1e5

分析

对于每个询问的串,如果我们可以找到这个串出现的所有位置,那么就可以很容易解决这个问题.

用n个询问的字符串 m i m_i mi建AC自动机. 我们可以对字符串s每一个位置都暴力跳fail,找到有哪些询问串以这个位置结尾.
但是暴力跳fail,时间复杂度为O( n 2 n^2 n2),如果s串是1 e 5 e^5 e5 个a组成的串,m串也1 e 5 e^5 e5个a .那么暴力跳fail将瞬间爆炸
因此我们还需要进一步优化:

我们定义match数组:match[i]为i节点的最长后缀,并且这个后缀是n个询问串中的一个.如果不存在这样的后缀那么match[i]为AC自动机的根节点.

由于限制了询问字符串的总长度,而长度不同的字符串不超过 ∑ 1 n ∣ m i ∣ \sqrt{\sum_1^n|m_i|} 1nmi 个,而每一次跳match数组,当前节点所表示的字符串长度至少减小1,因此最多会跳O( n \sqrt n n ) 次,
由于一共只跳 n n n\sqrt n nn 次,因此所有询问串的出现位置 也不会超过 n n n\sqrt n nn ,我们有足够的空间存下所有的出现位置.
最后我们可以在 O ( n n ) O(n\sqrt n) O(nn ) 内找出所有询问串的出现位置存入pos数组,最后遍历pos数组得出答案.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
typedef vector<int> vi;
#define debug(x) cerr<<#x<<' '<<x<<'\n'
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
const int maxn=1e5+10;
const int mod=1e9+7;
const int inf=0x3f3f3f3f;
struct Trie {
    int next[maxn][26],fail[maxn],id[maxn],root,cnt;
    int match[maxn];
    int newnode() {
        rep(i,0,25) next[cnt][i]=-1;
        id[cnt++]=0;
        return cnt-1;
    }
    void init() {
        cnt=0;
        root=newnode();
    }
    void ins(char a[],int x) {
        int len=strlen(a),now=root;
        rep(i,0,len-1) {
            if(next[now][a[i]-'a']==-1)
                next[now][a[i]-'a']=newnode();
            now=next[now][a[i]-'a'];
        }
        id[now]=x;
    }
    void build() {
        queue<int> q;
        fail[root]=root;
        rep(i,0,25) {
            if(next[root][i]==-1) {
                next[root][i]=root;
            }
            else {
                fail[next[root][i]]=root;
                match[next[root][i]]=root;
                q.push(next[root][i]);
            }
        }
        while(!q.empty()) {
            int now=q.front();
            q.pop();
            rep(i,0,25) {
                if(next[now][i]==-1) next[now][i]=next[fail[now]][i];
                else {
                    fail[next[now][i]]=next[fail[now]][i];
                    q.push(next[now][i]);
                    int tmp=fail[next[now][i]];
                    if(id[tmp]>0) match[next[now][i]]=tmp;
                    else match[next[now][i]]=match[tmp];
                }
            }
        }
    }
}AC;
vi pos[maxn];
char str[maxn],a[maxn];
int k[maxn],lena[maxn];
int main()
{
    ios::sync_with_stdio(false);cin.tie(0);
    cin>>str;
    int n;
    cin>>n;
    AC.init();
    rep(i,1,n) {
        cin>>k[i]>>a;
        lena[i]=strlen(a);
        AC.ins(a,i);
    }
    AC.build();
    int len=strlen(str),now=0;
    rep(i,0,len-1) {
        now=AC.next[now][str[i]-'a'];
        for(int t=now;t;t=AC.match[t]) {
            if(AC.id[t]) pos[AC.id[t]].pb(i);
        }
    }
    rep(i,1,n) {
        if(k[i]>pos[i].size()) cout<<-1<<'\n';
        else {
            int ans=inf;
            for(int l=0,r=k[i]-1;r<pos[i].size();l++,r++) {
                ans=min(ans,pos[i][r]-pos[i][l]+lena[i]);
            }
            cout<<ans<<'\n';
        }
    }
    return 0;
}
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值