字典树哇 AC自动机哇 = _ =

字典树哇 AC自动机哇 = _ =

例题 HDU 1251 统计难题

解题思路 :

字典树 原理:按照每个根向下发散 形成一棵 树
这个题 需要在每一个字母处都做统计 (求前缀单词)
开一个 二维数组和ant来 模拟树 root开始为0 作为 起点 t=str[i]-‘a’; 作为分支
关键就是 ant 这是形成树的关键 ant用来区分每一个节点 这样二维数组 tree才能
真正构建完成(很神奇…)

代码如下

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
const ll maxn=1e6+10;
int tree[maxn][26]; 构建父子关系
int cnt=0;//节点区分
int root;
int sum[maxn]={0};//统计数组
void insert(char s[]){
	int t;
	root=0;//必须起点为0 来表示树的开始
	for(int i=0;s[i];i++){
		t=s[i]-'a';//转化 数字 便于二维数组 模拟
		if(!tree[root][t]) tree[root][t]=++cnt;
		//节点构建成真实 不同数值便于区分   
		sum[tree[root][t]]++;//统计每一(节点/字母)
		root=tree[root][t];//子节点变成父节点
	}
}
int find(char s[]){
	int t;
	root=0;
	for(int i=0;s[i];i++){
		t=s[i]-'a';
		if(!tree[root][t])
		return 0;
		root=tree[root][t];
	}
	return sum[root];
}
int main()
{
	char s[maxn],ask[maxn];
	while(1){
		gets(s);
		if(s[0]=='\0')
		break;
		else
		insert(s);//构树函数
	}
	while(cin>>ask){//查询函数
		cout<<find(ask)<<endl;
	}
     return 0;
}

例题 HDU - 4825 Xor Sum

解题思路及注意事项:

依旧是字典树 按照 0/1建树 不过是从尾部往前找 (因为2进制)
查询时 因为 求异或最大值所以 要求二进制对应每位应该不同
(建树 数组 应该开 要求的样例*13+ 不知道怎么求得的13+)我直接乘了32
具体解析 嵌入代码中了

代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<math.h>
using namespace std;
typedef long long ll;
const ll maxn=1e5+7;
int a[maxn],b[maxn];
int tree[maxn*32][2]; //构建父子关系
int cnt;//节点区分
int root;
void init()
{
	cnt=0;//节点标记重置 
	memset(tree,0,sizeof(tree)); //树重置 
}
void insert(int x){
	int t;
    root=0;//必须起点为0 来表示树的开始
	for(int i=31;i>=0;i--){//从尾部向前 
		t=(x>>i)&1;//获得每一位数字(0/1) 
		if(tree[root][t]==0) tree[root][t]=++cnt;
		root=tree[root][t];
	}
}
int find(int y){
	int t,ans=0;
	root=0;
	for(int i=31;i>=0;i--){
		t=!((y>>i)&1);//异或最大  要求对应为 不同 
		if(tree[root][t]) //求最大值 ans 
		 ans+=(t*(1<<i)),root=tree[root][t];
		else
		ans+=(!t*(1<<i)),root=tree[root][!t];
		//不存在 +不存在的对应的反值(一定存在);
		//根的指向 -->同理 
	}
	return ans;
}
int main()
{
	int t;
	//所有输入输出必须用scanf();以及 printf();不然超时 -_- 
	scanf("%d",&t);
	for(int j=1;j<=t;j++){
		init(); 
		int n,m;
		scanf("%d%d",&n,&m);
		for(int i=0;i<n;i++){
			scanf("%d",&a[i]);
			insert(a[i]);
		}
		for(int i=0;i<m;i++)
			scanf("%d",&b[i]);
	    printf("Case #%d:\n",j);
		for(int i=0;i<m;i++){
			int ans=find(b[i]);
			printf("%d\n",ans);
		}	
	}
     return 0;
}

例题 HDU 2222 Keywords Search

问题分析:

AC自动机(多模式串匹配)的模板题 (感较很难哇…好久才理解)
ac自动机,其中 有字典树的内容,kmp的思想
字典树:建立树优化 查询
fail(失败指针):匹配失败是直接指向一个最长后缀
具体看代码内分析(很详细)
我学习的视频链接(代码来源)
B站大佬AC自动机

AC代码如下:

#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int maxx=1e6+10;// 建树节点个数 
const int maxn=500005; //建树 大小 
struct state{//每个节点参数  
	int next[26];//下一节点 
	int fail,cnt;// 失败指向节点存贮    --   节点区分标志 
}p[maxn];//建树结构体大小 

int size;//节点区分参数 

void init(){//多组输入  初始化 

for(int i=0;i<maxn;i++){
memset(p[i].next,0,sizeof(p[i].next));//初始化 指向 
	p[i].fail=p[i].cnt=0;// 初始化失败指向  --   初始化节点标记 
}
size=1;//  首节点 复赋值 
}
queue<int> q;  //构建fail队列创建 
void insert( char*s){
	int n=strlen(s);
	int now=0;
	for(int i=0;i<n;i++){
		char c=s[i];
		if(!p[now].next[c-'a'])//如果不在树中 
		 p[now].next[c-'a']=size++;//给与节点 变成树中一部分 
		now=p[now].next[c-'a'];//子节点变成新的父节点 
	}
	p[now].cnt++; //统计 单词 出现的次数 
}
void build(){
	p[0].fail=-1; //0项失败没有指向 
	q.push(0);//送入最初值 
	while(q.size()){   
		int u=q.front(); //队列首元素 
		q.pop();  
		for(int i=0;i<26;i++){ //扫描 队首元素的下一子节点 
			if(p[u].next[i]){  //如果存在 
				if(u==0)  //为祖先 (就是0) 
				p[p[u].next[i]].fail=0;//指向祖先 
				else 
				{
					int v=p[u].fail;//赋值 失败指向位置 
				while(v!=-1){//不为祖先(就是0的指向)失败指向 
					if(p[v].next[i]){//存在 
						p[p[u].next[i]].fail=p[v].next[i];
						//  (存在点<父类>的指向节点<子类>)
						//的失败指向 (失败指向的节点<自雷>) 
						break;
					}
					v=p[v].fail;//指向自己的失败指向 
				}
				if(v==-1)//为祖先 
				p[p[u].next[i]].fail=0;
				}
			q.push(p[u].next[i]);//送入先的节点(子类)作为父类 构建fail 
		}
	}
}
}
int get(int u){//防重复计算 (先看下面的match) 
	int res=0;
	while(u){
		res+=p[u].cnt;//累加统计 
		p[u].cnt=0;//结尾重置 
		u=p[u].fail;//结尾直接指向fail优化查找 
	}
	return res;
}
int match(char *s){//字符串查找 
	int n=strlen(s);
	int res=0,now=0;
	for(int i=0;i<n;i++){
		char c=s[i];
		if(p[now].next[c-'a'])//存在指向父类 
		now=p[now].next[c-'a'];
		else{//不存在使用fail跳转 
			int pp=p[now].fail;//失败指向节点 赋值 
			while(pp!=-1&&p[pp].next[c-'a']==0)
			//不为祖先(0)并且父类的子节点存在 
			    pp=p[pp].fail;//直至走到 祖先或者树分支的结尾 
			if(pp==-1)//最终fail指向祖先 
			now=0;//指向祖先 
			else 
			now=p[pp].next[c-'a'];//指向fail 
		}
		if(p[now].cnt)//是都为树分支结尾(单词最后一个单词节点) 
		res+=get(now);//次数累加到总计 
	}
	return res;
}
	/*当时自己看的这段很懵逼 **** 
	为啥没有一个一个和字符串 
	比较  ...现在想想,笑哭 ```
	1.其时有比较操作请看 for()循环的第一个 if()呢就是
	比较操作
	2.而后来的else  是fail的跳转 作用就是用来优化比较操作
	这一点和  kmp非常相似  
	*/
char s[maxx];//创建单词数组 
int main()
{
	int t,n; 
	scanf("%d",&t);
	while(t--){
		init();
		scanf("%d",&n);
		for(int i=0;i<n;i++){
			scanf("%s",s);
			insert(s);//构建每一个单词到树中 
		}
		scanf("%s",s);
		build();//树中各节点 用fail串联  的创建 
		printf("%d\n",match(s));
	}
	return 0;
}
发布了55 篇原创文章 · 获赞 1 · 访问量 988

猜你喜欢

转载自blog.csdn.net/weixin_43556527/article/details/102975915