[HNOI2006]最短母串问题
好题。
首先建立AC自动机。然后就不会了。
观察到很小,是状压的级别。
考虑将自动机中的变量升级为变量,表示从当前节点出发,能否到达各字符串。
则时,有
void ins(int id){
int x=1;
for(int i=0;i<S;i++){
if(!t[x].ch[s[i]-'A'])t[x].ch[s[i]-'A']=++cnt;
x=t[x].ch[s[i]-'A'];
}
t[x].state|=(1<<id);
}
注意到最后一句的变化。
在时,因为该节点的树上的所有祖先都是可到达的,我们就可以暴力回跳更新。
即
void build(){
for(int i=0;i<26;i++){
if(t[1].ch[i])q.push(t[1].ch[i]),t[t[1].ch[i]].fail=1;
else t[1].ch[i]=1;
}
while(!q.empty()){
int x=q.front();q.pop();
for(int i=0;i<26;i++){
if(t[x].ch[i])t[t[x].ch[i]].fail=t[t[x].fail].ch[i],q.push(t[x].ch[i]);
else t[x].ch[i]=t[t[x].fail].ch[i];
}
int y=t[x].fail;
while(y!=1&&!t[y].state)y=t[y].fail;
t[x].state|=t[y].state;
}
}
就是最后几行,暴力跳到树中第一个有值的祖先(再往前的祖先的答案已经存在了现在这个祖先的里)。
然后就是爆搜,从根开始,按照字典序bfs。
void bfs(){
Q.push(node(1,0,cnt=1));
vis[1][0]=true;
while(!Q.empty()){
node x=Q.front();Q.pop();
if(x.state==MAXN-1){print(x.id);exit(0);}
for(int i=0;i<26;i++){
int nstate=x.state|t[t[x.pos].ch[i]].state;
if(vis[t[x.pos].ch[i]][nstate])continue;
vis[t[x.pos].ch[i]][nstate]=true;
from[++cnt]=x.id;
way[cnt]=i;
Q.push(node(t[x.pos].ch[i],nstate,cnt));
}
}
}
是一个结构体,第一维意为这个状态在trie中的节点编号,第二维就是前文所述的“状态”,第三维是它新的编号(在倒推路径时用到)。
数组储存各个状态有没有被访问过,这可以避免重复搜索。
和是路径数组。
函数调用和输出答案。
为,即全状态。
代码:
#include<bits/stdc++.h>
using namespace std;
int n,cnt=1,S,from[2501000],MAXN;
char s[110],way[2501000];
bool vis[610][5010];
struct AC_Automaton{
int ch[26],state,fail;
}t[610];
void ins(int id){
int x=1;
for(int i=0;i<S;i++){
if(!t[x].ch[s[i]-'A'])t[x].ch[s[i]-'A']=++cnt;
x=t[x].ch[s[i]-'A'];
}
t[x].state|=(1<<id);
}
queue<int>q;
void build(){
for(int i=0;i<26;i++){
if(t[1].ch[i])q.push(t[1].ch[i]),t[t[1].ch[i]].fail=1;
else t[1].ch[i]=1;
}
while(!q.empty()){
int x=q.front();q.pop();
for(int i=0;i<26;i++){
if(t[x].ch[i])t[t[x].ch[i]].fail=t[t[x].fail].ch[i],q.push(t[x].ch[i]);
else t[x].ch[i]=t[t[x].fail].ch[i];
}
int y=t[x].fail;
while(y!=1&&!t[y].state)y=t[y].fail;
t[x].state|=t[y].state;
}
}
struct node{
int pos,state,id;
node(int x=0,int y=0,int z=0){
pos=x,state=y,id=z;
}
};
void print(int pos){
if(from[pos]!=1)print(from[pos]);
putchar(way[pos]+'A');
}
queue<node>Q;
void bfs(){
Q.push(node(1,0,cnt=1));
vis[1][0]=true;
while(!Q.empty()){
node x=Q.front();Q.pop();
if(x.state==MAXN-1){print(x.id);exit(0);}
for(int i=0;i<26;i++){
int nstate=x.state|t[t[x.pos].ch[i]].state;
if(vis[t[x.pos].ch[i]][nstate])continue;
vis[t[x.pos].ch[i]][nstate]=true;
from[++cnt]=x.id;
way[cnt]=i;
Q.push(node(t[x.pos].ch[i],nstate,cnt));
}
}
}
int main(){
scanf("%d",&n),MAXN=(1<<n);
for(int i=0;i<n;i++)scanf("%s",s),S=strlen(s),ins(i);
build();
bfs();
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?