Banned Patterns 计蒜客 - A1533

探讨使用AC自动机解决多模式字符串匹配问题,包括动态开点、映射转换和复杂度分析。通过将字母位置差转化为数字串,实现高效匹配,尽管存在复杂度证明难题。

这个题,感觉特别神奇,感觉复杂度是正确的,但是给不出特别正确的证明。

题意:给你n个匹配串,给你m次询问,然后问询问串里有没有子串是某个匹配串的映射?

最开始就感觉多模式匹配肯定是ac自动机嘛,然后发现映射情况数太多不好处理,就想有没有什么方法能消除字母与字母之间的影响,想了想口胡了一种做法,就是把考虑每个字母上次出现的位置,然后表示成位置差,这样一来每个串就可以o(1)的映射成一个唯一的数字串。

但是这样转化的话,首先是动态开店,不过感觉每个节点的指针不会太多,这个log还是很小的。再之后, 就感觉ac自动机的fail树不是特别好建,因为动态开点的,很可能我有的指针我的fail没有。然后我脑子一热就让每个点继承上一个点的状态了,不是很会证这个复杂度,但是明显能感觉出很作死的样子,然后果然tle了。

去网上找了找题解,发现有一个大佬是差不多的思路,然后他是每次向上暴力跳fail的嘛,他也说给不出复杂度的证明。这个写法是能ac的。

考虑了一些情况,感觉暴力跳的话应该是达不到(n^2)的,这里给出一个不一定正确的想法。考虑只有一条链时的fail树,大概应该是o(n)的,因为在这一次我跳过的路径,其他点不会再跳一遍。然后考虑n个串的时候,感觉上最差是o(nsqrt(n))的,但是给不出证明。然后题目保证匹配串只有5000个,也不知道是不是这里的保证。

 

还有一个细节,因为是靠记录上一个出现的位置转化的字符串,这样在失配的时候很可能当前位置变成未出现过的了,要特判一下。

#include <bits/stdc++.h>

using namespace std;

#define N 200025
#define M 27
#define ll long long
#define mod 1000000007
#define go(i,a,b) for(int i=(a);i<=(b);i++)
#define dep(i,a,b) for(int i=(a);i>=(b);i--)
#define pb push_back
#define inf 0x3f3f3f3f
#define ld long double
#define pii pair<int,int>
#define vi vector<int>
#define add(a,b) (a+=(b)%mod)%=mod
#define lowb(c,len,x) lower_bound(c+1,c+len+1,x)-c
#define uppb(c,len,x) upper_bound(c+1,c+len+1,x)-c
#define ls i*2+1
#define rs i*2+2
#define mid (l+r)/2
#define lson l,mid,ls
#define rson mid+1,r,rs
//#define root 1,n,0
#define ms(a,b) memset(a,b,sizeof a)
#define muti int T,cas=1;cin>>T;while(T--)
#define lll __int128
#define si short int
#define fi first
#define se second
#define l(x) (x&-x)
#define G(x) for( int i=h[x];i;i=eg[i].y )
map<int,int>::iterator it;
struct no{
    int en[N],len[N],fail[N],l,root,now;
    map<int,int>ch[N];
    int newnode(int dep){
        ch[l].clear();en[l]=0;len[l]=dep;
        return l++;
    }
    void init(){
        l=0;
        root=newnode(0);
    }
    int getlast(int u,int d){
        if(d>=len[u]+1)d=0;
        while(u!=root&&!ch[u].count(d)){
            u=fail[u];
            if(d>=len[u]+1)d=0;
        }
        return ch[u][d];
    }
    void inset(int d){
        //cout<<d<<endl;
        if(!ch[now].count(d))ch[now][d]=newnode(len[now]+1);
        //cout<<now<<' '<<l<<endl;
        now=ch[now][d];
        //cout<<now<<endl;
    }
    void build(){
        queue<int>q;
        fail[root]=0;
        it=ch[root].begin();
        while(it!=ch[root].end()){
            fail[it->se]=root;
            q.push(it->se);
            it++;
        }
        while(!q.empty()){
            int now=q.front();q.pop();
            en[now]|=en[fail[now]];
         //   cout<<now<<" "<<fail[now]<<"++++++++"<<endl;
            it=ch[now].begin();
            while(it!=ch[now].end()){
                fail[it->se]=getlast(fail[now],it->fi);
             //   cout<<it->fi<<' '<<it->se<<endl;
                q.push(it->se);
                it++;
            }
        }
    }
    int query(int d){
        if(d>len[now])d=0;
        now=getlast(now,d);
        return en[now];
    }
}ac;
int n,m,las[M];
char s[N];
int main()
{
    muti{
        scanf("%d",&n);
        ac.init();
        go(i,1,n){
            go(j,0,25)las[j]=0;
            scanf("%s",s+1);
            int len=strlen(s+1);
            ac.now=ac.root;
            go(j,1,len){
                if(!las[s[j]-'A'])ac.inset(0);
                else ac.inset(j-las[s[j]-'A']);
                las[s[j]-'A']=j;
            }
            ac.en[ac.now]=1;
        }
       // cout<<1111111111111<<endl;
        ac.build();
        scanf("%d",&m);
        printf("Case #%d:",cas++);
        go(i,1,m){
            go(j,0,25)las[j]=0;
            scanf("%s",s+1);
            int len=strlen(s+1),flag=0;
            ac.now=ac.root;
            go(j,1,len){
                if(!las[s[j]-'A'])flag=ac.query(0);
                else flag=ac.query(j-las[s[j]-'A']);
                las[s[j]-'A']=j;
                if(flag)break;
            }
            printf(" %c",flag?'Y':'N');
        }
        cout<<endl;
    }
}
源码来自:https://pan.quark.cn/s/a4b39357ea24 《C++ Primer》作为C++编程领域中的一部权威著作,主要服务于初学者和经验丰富的开发者,致力于帮助他们深入掌握C++的核心知识。 第一章通常会详细讲解C++语言的基础概念和语法结构,包括变量的使用、数据类型的分类、常量的定义、运算符的应用以及基础的输入输出操作。 接下来,我们将对这一章中的核心知识点和可能的习题解答进行深入分析。 ### 1. 变量与数据类型在C++编程中,变量被视为存储数据的媒介。 每一个变量都必须预先声明其数据类型,常见的数据类型有整型(int)、浮点型(float)、双精度浮点型(double)以及字符型(char)。 例如:```cppint age = 25; // 声明一个整型变量age并赋予其初始值25float weight = 70.5f; // 声明一个浮点型变量weight并赋予其初始值70.5char grade = A; // 声明一个字符型变量grade并赋予其初始值A```### 2. 常量与字面量常量指的是不可更改的值,可以通过`const`关键字进行声明。 例如:```cppconst int MAX_SIZE = 100; // 声明一个整型常量MAX_SIZE,其值为100```字面量是指程序中直接书写的值,如`42`、`3.14`或`"Hello"`。 ### 3. 运算符C++提供了多种运算符,涵盖了算术运算符(+,-,*,/,%)、比较运算符(==,!=,<,>,<=,>=)、逻辑运算符(&&,||,!)以及赋值运算符(=,+=,-=,*=,/=,%=)等。 ### 4. 输入与输出在C++中,使用`std::cin`来实现输...
内容概要:本文详细介绍了一个基于C++的仓库存储管理系统的设与实现,涵盖了项目背景、目标、挑战及解决方案,并系统阐述了整体架构设、数据库建模、功能模块划分、权限安全、并发控制、数据一致性保障、异常处理与可扩展性等关键内容。通过面向对象编程思想,采用分层架构与模块化解耦设,结合STL容器、多线程、锁机制等C++核心技术,实现了高效的库存管理功能,包括入库、出库、盘点、调拨、权限控制、日志追踪与智能报表分析。文中还提供了核心类如Inventory(库存)、User(用户权限)、LogEntry(操作日志)及WarehouseManager(主控制器)的代码示例,展示了数据结构设与关键算法逻辑。; 适合人群:具备C++编程基础,熟悉面向对象设与基本数据结构的软件开发人员,尤其适合从事企业级管理系统开发或希望深入理解系统架构设的中级开发者(工作1-3年);也适用于算机相关专业学生进行课程设或毕业项目参考; 使用场景及目标:①学习如何使用C++构建复杂业务系统的整体架构与模块划分方法;②掌握高并发、数据一致性、权限控制、异常处理等企业级系统关键技术的实现思路;③理解仓储管理业务流程及其在软件系统中的建模与落地方式;④为开发类似ERP、MES等后台管理系统提供技术原型与设参考; 阅读建议:此资源不仅提供理论架构与代码片段,更强调系统设的完整性与工程实践性。建议读者结合代码示例动手实现核心模块,深入理解类之间的关系与交互逻辑,重点关注多线程安全、事务管理与权限校验等难点环节,并尝试扩展功能如对接GUI界面或数据库持久化模块,以全面提升系统开发能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值