熟悉的文章(后缀自动机+二分答案+单调队列)

熟悉的文章

题意

给定一本包含 M M M个字符串( 01 01 01串)的字典,然后给出 N N N个字符串,要求输出一个最大的长度 L L L。其中 L L L满足当前字符串 90 90 90%以上的部分都能被字典中的字符串的子串(子串长度不小于 L L L)表示。

思路

  1. 既然是与子串相关的问题,先考虑建立后缀自动机(在字典中每个字符串中间插入不会出现的字符进行字符串的分割),这样就能进行子串的匹配。
  2. 题目要求最大的 L L L,由于 L L L越小肯定容易合理,也就是 L L L是否合理是与 L L L的大小是呈单调性的,我们自然而然地会想到采用二分答案。
  3. 而对于每个字符串,它的每一个位置( i i i)都对应了一个以当前位置结尾的最长可识别后缀(长度为 r l [ i ] rl[i] rl[i]),这个后缀长度显然可以预处理得到(一种常用技巧)。
  4. 具体到如何分配字符串的哪些部分为可识别以最小化不可识别的部分,我们可以采用 d p dp dp;设 d p [ i ] dp[i] dp[i]为字符串 [ 1 , i ] [1,i] [1,i]中不可识别的字符的最小数量,则转移方程为: d p [ i ] = m i n ( d p [ i 1 ] , d p [ j ] ) dp[i]=min(dp[i-1],dp[j]) dp[i]=min(dp[i1],dp[j]),其中 j j j满足 [ j + 1 , i ] [j+1,i] [j+1,i]为一个可识别的子串(长度大于等于 L L L),而怎样得到最优的 j j j使得 d p [ i ] dp[i] dp[i]最小呢?
  5. 有一个很明显的观察: i r l [ i ] i-rl[i] irl[i]是单调不减的(其实也不明显,但很容易验证)。同时,对于两个位置 a , b a,b a,b,若 a < b a<b a<b d p [ a ] > d p [ b ] dp[a]>dp[b] dp[a]>dp[b],则 d p [ a ] dp[a] dp[a]显然在任何时候都不会成为最优的 j j j。以上的描述表明我们可以维护一个单调递增的队列!
  6. 这样,我们就可以在 O ( n l o g n ) O(nlogn) O(nlogn)的时间复杂度内处理单个字符串询问。

题面描述

#include "bits/stdc++.h"
#define hhh printf("hhh\n")
#define see(x) (cerr<<(#x)<<'='<<(x)<<endl)
using namespace std;
typedef long long ll;
typedef pair<int,int> pr;
inline int read() {int x=0;char c=getchar();while(c<'0'||c>'9')c=getchar();while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();return x;}

const int maxn = 6e6+10;
const int inf = 0x3f3f3f3f;
const int mod = 1e9+7;
const double eps = 1e-7;

int N, M, ml;
char s[maxn];
int ch[maxn][3], fa[maxn], len[maxn];
int last=1, tot=1;
int dp[maxn], que[maxn], rl[maxn];

void add(int c) {
    int p=last, np=last=++tot;
    len[np]=len[p]+1;
    for(; p&&!ch[p][c]; p=fa[p]) ch[p][c]=np;
    if(!p) fa[np]=1;
    else {
        int q=ch[p][c];
        if(len[q]==len[p]+1) fa[np]=q;
        else {
            int nq=++tot; len[nq]=len[p]+1;
            fa[nq]=fa[q], fa[np]=fa[q]=nq;
            memcpy(ch[nq],ch[q],12);
            for(; p&&ch[p][c]==q; p=fa[p]) ch[p][c]=nq;
        }
    }
}

void pre() {
    int curlen=0, now=1, l=strlen(s+1);
    for(int i=1; i<=l; ++i) {
        int c=s[i]-'0';
        if(ch[now][c]) curlen++, now=ch[now][c];
        else {
            for(; now&&!ch[now][c]; now=fa[now]);
            if(now) curlen=len[now]+1, now=ch[now][c];
            else curlen=0, now=1;
        }
        rl[i]=curlen;
    }
}

bool check(int L) {
    int l=strlen(s+1), head=1, tail=0;
    for(int i=1; i<=l; ++i) {
        dp[i]=dp[i-1]+1;
        if(i>=L) {
            while(head<=tail&&dp[que[tail]]>=dp[i-L]) tail--;
            que[++tail]=i-L;
        }
        while(head<=tail&&que[head]<i-rl[i]) head++;
        if(head<=tail) dp[i]=min(dp[i],dp[que[head]]);
    }
    if(dp[l]<=l/10) return 1;
    return 0;
}

int main() {
    //ios::sync_with_stdio(false); cin.tie(0);
    scanf("%d%d", &N, &M);
    for(int i=1; i<=M; ++i) {
        scanf("%s", s);
        int l=strlen(s);
        if(l>ml) ml=l;
        for(int i=0; i<l; ++i) add(s[i]-'0');
        add(2);
    }
    while(N--) {
        scanf("%s", s+1); pre();
        int l=1, r=ml+1, m=(l+r)/2;
        while(l<r) {
            if(check(m)) l=m+1;
            else r=m;
            m=(l+r)/2;
        }
        cout<<m-1<<endl;
    }
}
全部评论

相关推荐

01-19 12:48
门头沟学院 C++
只想搞钱的鸽子很喜欢...:混账是很多的,还有那些在自己风华正茂的年纪说风凉话讥讽那些下岗前员工的。这些人都是现在职场环境这么烂的帮凶
点赞 评论 收藏
分享
2025-12-27 18:11
已编辑
门头沟学院 前端工程师
28双非鼠鼠第一份实习,感谢金山,感谢面试官张先生的赏识,也感谢自己很开心很开心(有没有待过的前辈,求摸鱼技巧bushi)timeline12.15&nbsp;投递12.16&nbsp;约面12.18&nbsp;一面&nbsp;半个小时后约二面12.19&nbsp;二面,口头oc12.24&nbsp;发offer一面1.&nbsp;开发页面中使用的布局方式2.&nbsp;flex:&nbsp;1&nbsp;是什么的缩写3.&nbsp;水平居中的方法4.&nbsp;tailwindcss&nbsp;的优势5.&nbsp;js&nbsp;的闭包6.&nbsp;打印结果的题,解释为什么(var&nbsp;定义&nbsp;i&nbsp;,setTimeout&nbsp;执行打印),使用&nbsp;let&nbsp;的打印结果7.&nbsp;箭头函数和普通函数的区别8.&nbsp;promise&nbsp;构造函数是同步还是异步9.&nbsp;内存泄漏的情况10.&nbsp;interface&nbsp;和&nbsp;type&nbsp;的区别11.&nbsp;react&nbsp;的&nbsp;key&nbsp;作用12.&nbsp;常用的钩子函数13.&nbsp;怎么避免不必要的渲染14.&nbsp;useeffect&nbsp;的使用场景15.&nbsp;react&nbsp;和&nbsp;vue&nbsp;怎么选择16.&nbsp;vue&nbsp;的&nbsp;data&nbsp;为什么用函数17.&nbsp;tcp&nbsp;为什么需要三次握手和四次挥手18.&nbsp;vite&nbsp;为什么比较快19.&nbsp;解释防抖节流和手写防抖函数,还有实现思路20.&nbsp;深浅拷贝的区别和手写深拷贝,讲实现思路反问了业务,反馈时间和学习建议二面基本上是围绕项目展开,根据项目的每一项,来给场景题问你会怎么做,跟基础相关的东西如下:1.&nbsp;虚拟列表的实现和原理2.&nbsp;zustand&nbsp;和&nbsp;context&nbsp;的区别3.&nbsp;vitest&nbsp;相关,写测试的话应该怎么做些什么?4.&nbsp;monorepo的细节问题5.&nbsp;做项目的动机6.&nbsp;事件委托和时间冒泡的区别有个点顺着问了我五个问题实在是答不下去了就是说感觉金山云这边面试虽然一面全是八股,但是二面还是要好好准备项目,做到能被深挖那么两三个问题的程度,鼠鼠也是运气很好,问的都是准备过的嘻嘻面试完之后还很期待这个面试官会不会是我mt或者ld,会很认真的听我说话,然后告诉我哪里有小问题,不知道是不是鼠鼠的错觉,感觉他看后辈的眼神都是带有欣赏的意味真的很复合我对mt/ld的幻想(bushi),但是后来发现他ip是北京的qwq有点点小失落,不过没关系,看隔壁某书感觉金山的节奏还挺慢的期待入职ing愿一切顺利,好运常伴吾身这里再吐槽一下流程,怎么!!这么!!慢!!急死我了急死我了!!鬼知道我从周一到接到offer这段时间有多煎熬,哎呀但是但是好在一切如愿
发面经攒人品
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务