[HNOI2006]最短母串问题

[HNOI2006]最短母串问题

好题。

首先建立AC自动机。然后就不会了。

观察到n很小,是状压的级别。

考虑将自动机中的fin变量升级为state变量,表示从当前节点出发,能否到达各字符串。

ins时,有

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);
}

注意到最后一句的变化。

build时,因为该节点的fail树上的所有祖先都是可到达的,我们就可以暴力回跳更新state

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;
	}
}

就是最后几行,暴力跳到fail树中第一个有值的祖先(再往前的祖先的答案已经存在了现在这个祖先的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));
		}
	}
}

node是一个结构体,第一维pos意为这个状态在trie中的节点编号,第二维state就是前文所述的“状态”,第三维id是它新的编号(在倒推路径时用到)。

vis数组储存各个状态有没有被访问过,这可以避免重复搜索。

fromway是路径数组。

print()函数调用fromway输出答案。

(MAXN1)(2n1),即全1状态。

代码:

#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;
}
posted @   Troverld  阅读(115)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示