• 字典树哇 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]=='')
    		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:
    ",j);
    		for(int i=0;i<m;i++){
    			int ans=find(b[i]);
    			printf("%d
    ",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
    ",match(s));
    	}
    	return 0;
    }
    
  • 相关阅读:
    kafka官方的kafka-server-start.sh不能关闭kafka进程解决办法
    Linux如何切换图形界面和命令行界面
    2019-9-28:渗透测试,基础学习,DNS投毒
    2019-9-28:渗透测试,基础学习,pgp常量,逻辑运算,DNS投毒,笔记
    2019-9-10:渗透测试,基础学习,nmap扫描命令,php基本语法学习,笔记
    2019-9-11:渗透测试,Kill远控软件,初接触
    2019-9-27:渗透测试,metasploit-framework初接触
    2019-9-26:渗透测试,基础学习,js正则以及什么是目录扫描,笔记
    2019-9-26:渗透测试,基础学习,nmap扫描kali虚拟机服务
    2019-9-25:渗透测试,基础学习,Hydra BP爆破,js基本知识,banner信息收集笔记
  • 原文地址:https://www.cnblogs.com/maxv/p/11569047.html
Copyright © 2020-2023  润新知