[HNOI2004] L 语言 题解(AC 自动机上 dp)

前言:

原版数据超弱,爆搜就能过(即洛谷里面 80 分的数据),在此不多说,这里讲的是正解。(如果不是正解我还敢写题解吗)

唔······话说洛谷里的题解用的都有状压,蒟蒻表示这题不用状压也能过(欢迎各位大佬 hack 我的做法,把我的做法弄到 TLE)。

正文:

si 表示文章的第 i 个字母,dpi 表示文章前 i 位是否能被理解(为 1 表示能理解,否则不能)。那么如果 sisi+1···sj 能匹配某个单词,且 dpi1=1,则 dpj=1

但是数据范围不小,暴力枚举前面的那些字母去匹配是会超时的。看到多模式串,我们肯定得想到 AC 自动机喽。

然鹅······

大家可以看到,不动脑子地套上 AC 自动机后虽然比暴力多了 5 分,但还是不能通过全部数据。

我在交这份代码前也意识到了这点,所以又写了些优化,于是就过了。

讲下优化吧:

  1. 在算 dpi 的时候,如果它已经可以为 1,就跳出循环。

  2. 在算 dpi 的时候,如果之前最多能匹配到的那一位离现在差的位数大于模式串长度的最大值,直接输出答案。举个例子,如今在算第 100 位的答案,但是当前最多只能匹配到第 10 位,那么就没必要继续算了,答案就取 10。因为你至少需要长度为 90 的模式串才可能让当前长度能匹配,但是找不到这么长的模式串,所以再往后就更不可能了。

  3. 在跳失配数组的时候,其中很多对答案都没有贡献(即不在叶子结点的),所以我们在求失配数组时,可以再开个数组,把实际该跳到哪里给记录下来,这个做法在 P3796 【模板】AC 自动机(加强版)中有人提及。不过我的代码经过实测,不加该优化也可以 AC,我下面放的代码也是没有这个优化的。当然,加上它应该会更快些。

Code:

#include<bits/stdc++.h>
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3f
#define inf 0x7fffffff
#define INF 0x3f
#define v e[i].y
using namespace std;
inline ll read(){
    char ch=getchar();ll x=0,w=1;
    while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+ch-48,ch=getchar();return w==1?x:-x;
}
inline void write(ll x){
    if(x<0)x=-x,putchar('-');
    if(x<10){putchar(48+x);return;}
    write(x/10),putchar((x+10)%10+48);
}
int n=read(),m=read(),q[100005],ans,h,t,maxl;
bool dp[2000005];
char xk[2000005];
struct AC{
    int ch[30005][30],Fail[30005],val[30005],num;
    void Insert(int L){
        int op=0;
        for(int i=1,j;i<=L;i++){
            j=xk[i]-'a';
            op=(ch[op][j]?ch[op][j]:ch[op][j]=++num);
        }
        val[op]=L,maxl=max(maxl,L);
    }
    void getfail(){
        h=0,t=0;
        for(int i=0;i<26;i++)if(ch[0][i])q[++t]=ch[0][i],Fail[ch[0][i]]=0;
        while(h<t){
            int u=q[++h];
            for(int i=0;i<26;i++){
                if(ch[u][i])Fail[ch[u][i]]=ch[Fail[u]][i],q[++t]=ch[u][i];
                else ch[u][i]=ch[Fail[u]][i];
            }
        }
    }
    int solve(int L){
        for(int i=1;i<=L;i++)dp[i]=0;
        dp[0]=1,ans=0;
        int op=0;
        for(int i=1,j;i<=L;i++){
            if(ans+maxl<i)break;
            j=xk[i]-'a',op=ch[op][j];
            for(int k=op;k;k=Fail[k]){
                dp[i]|=dp[i-val[k]];
                if(dp[i])break;
            }
            if(dp[i])ans=i;
        }
        return ans;
    }
}A;
int main(){
    while(n--)scanf("%s",xk+1),A.Insert(strlen(xk+1));
    A.getfail();
    while(m--)scanf("%s",xk+1),write(A.solve(strlen(xk+1))),putchar(10);
    return 0;
}
posted @   mcDinic  阅读(279)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示