序列自动机
引入
给定一个长度为 的正整数序列 ,有 次询问,第 次询问给定一个长度为 的序列 ,请你判断 是不是 的子序列。序列 和所有 中的元素都不大于一个给定的正整数 。
,,,。
做法
考虑维护一个能够维护某个串的所有本质不同的子序列的自动机。
序列自动机
可能是最简单构造/理解的,附加内容最少的自动机。
构造
考虑贪心。对于一个串 ,从某个位置开始维护子序列时,显然让每个字符均在 尽可能靠前的位置出现时,后面能够接的子序列数会尽量多。这样不会减少能够构造的数量的子序列数量。
设 表示 之后第一个字符 出现的位置。将 抽象成转移边,则所有的 转移边构成了自动机。而 的求法也很简单,逆推即可:
字符集较大时,可以用主席树维护转移边。
模板代码
点此查看代码
#include <bits/stdc++.h> using namespace std; const int maxl=18; const int maxn=1000010; int n,m,q,i,lt,rt,mt,pt,qt,tot; int a[maxn]; struct node{ int ls,rs,val; }tr[maxn*maxl]; #define ls(p) tr[p].ls #define rs(p) tr[p].rs #define val(p) tr[p].val int root[maxn]; int main(){ scanf("%d",&n); scanf("%d%d%d",&n,&q,&m); for(i=1;i<=n;++i) scanf("%d",a+i); root[n]=tot=1; for(i=n;i;--i){ pt=root[i-1]=++tot; lt=1;rt=m;qt=root[i]; while(lt<rt){ tr[pt]=tr[qt]; mt=(lt+rt)>>1; if(a[i]<=mt){ ls(pt)=++tot; rt=mt;qt=ls(qt); } else{ rs(pt)=++tot; lt=mt+1;qt=rs(qt); } pt=tot; } val(pt)=i; } while(q--){ scanf("%d",&n); for(i=1;i<=n;++i) scanf("%d",a+i); pt=root[0]; for(i=1;i<=n;++i){ lt=1;rt=m; while(lt<rt){ mt=(lt+rt)>>1; if(a[i]<=mt){ pt=ls(pt); rt=mt; } else{ pt=rs(pt); lt=mt+1; } } pt=val(pt); if(!pt){ printf("No\n"); break; } else pt=root[pt]; } if(pt) printf("Yes\n"); } return 0; }
例题:
给出字符串 ,计算其本质不同的子序列数,答案取模。。
附加内容 1:无。
直接求自动机上不同路径数即可。
附加内容 2:求其 小子序列。。
附加内容 2.5:。
在子序列自动机的 DAG 上找出从某个点出发的路径条数(根节点之外的点的路径条数要加一),大于 的直接赋为 (打 标记),然后模拟 dfs 过程,按位填字符即可,方法可以参照 P3975 [TJOI2015]弦论 的做法。注意原题空间只有 125MB,故在 DAG 上 dp 找路径条数时,不应该建反图然后拓扑 dp,而是 dfs 以递归查找。
代码
点此查看代码
#include <bits/stdc++.h> using namespace std; const int maxn=1000010; const int maxx=1000000000; const int Inf=1000000001; int n,k,i,j,l,r,u,v,tot; int num[maxn],nxt[maxn][26]; char s[maxn]; bool vis[maxn]; void dfs(int p){ if(vis[p]) return; vis[p]=1; int lp,to; for(lp=0;lp<26;++lp){ to=nxt[p][lp]; if(!to) continue; dfs(to); num[p]+=num[to]; if(num[p]>maxx){ num[p]=Inf; return; } } } int main(){ scanf("%d%d%s",&n,&k,s+1); for(i=n;i;--i){ num[i]=1; for(j=25;j>=0;--j) nxt[i-1][j]=nxt[i][j]; nxt[i-1][s[i]-'a']=i; } dfs(0); ++k; for(;;){ if(k==1) return 0; --k; for(j=0;j<26;++j){ v=nxt[u][j]; if(!v) continue; if(k<=num[v]){ u=v; putchar(j+'a'); break; } else k-=num[v]; } } return 0; }
附加内容3: 组询问,每次询问第 小子序列的最后 个字符。。
待补。
本文来自博客园,作者:Fran-Cen,转载请注明原文链接:https://www.cnblogs.com/Fran-CENSORED-Cwoi/p/16775656.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】