ACM之路(15)—— 字典树入门练习
刷的一套字典树的题目链接:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=120748#overview
个人喜欢指针的字典树写法,但是大力喜欢数组的写法,反正是一个队的,互补一下反而更好- 。-本来前几题我的指针写法都是用new的,后来发现用new可能会超时,所以不如先静态的分配好足够的内存。因此,这样就需要和数组写法一样算节点数目了。引用一下铭神的原话:“节点数在最坏情况下可以认为是字符数量”,也就是说,节点大小应开单词数量乘以每个单词的长度上限。
A题,赤裸裸的字典树,直接给代码,但是不好的是,这题用的是new的方法创建新节点,要作模板的话不妨使用后面几题的代码。代码如下:
#include <stdio.h>
#include <algorithm>
#include <set>
#include <math.h>
#include <vector>
#include <stack>
#include <map>
#include <string.h>
#define t_mid (l+r>>1)
#define ls (o<<1)
#define rs (o<<1 | 1)
#define lson ls,l,t_mid
#define rson rs,t_mid+1,r
using namespace std;
typedef long long ll; struct node
{
int cnt;
node *child[];
node()
{
cnt=;
for(int i=;i<;i++) child[i]=NULL;
}
}; node *root = new node(); void Insert(char *s)
{
node *p = root;
int len = strlen(s);
for(int i=;i<len;i++)
{
int m = s[i] - 'a';
if(p->child[m] != NULL)
{
p = p->child[m];
p->cnt++;
}
else
{
node *newnode = new node();
p->child[m] = newnode;
p = newnode;
p->cnt++;
}
}
} int Search(char *s)
{
int len = strlen(s);
node *p = root;
for(int i=;i<len;i++)
{
int m = s[i] - 'a';
if(p->child[m] == NULL)
{
return ;
}
else p = p->child[m];
}
return p->cnt;
} int main()
{
char s[];
while(gets(s) != NULL && strcmp(s,""))
{
Insert(s);
}
while(gets(s) != NULL)
{
printf("%d\n",Search(s));
}
return ;
}
B题,把数字化成二进制,然后从高位到低位进行储存,然后匹配的时候,因为是异或,所以用相反的数字匹配就行(1的话匹配字典树里面0的路径,反之亦然)。
这题的代码一开始不知道怎么下手,还是借鉴了别人的代码的= =。代码如下:
#include <stdio.h>
#include <algorithm>
#include <string.h>
using namespace std;
typedef long long ll; const int N = ;
const int M = N * 1e5;
int n,m,p,root;
ll a[N]; struct Node
{
int l,r;
int val;
void clear()
{
l=r=-;
}
}node[M]; void Insert(int &root,int deep,ll x)
{
if(root == -)
{
root = p++;
node[root].clear();
} if(deep == -) {node[root].val = x;return;} if(x & a[deep]) Insert(node[root].r,deep-,x); //如果这个数的当前位是1,则向右边走
else Insert(node[root].l,deep-,x);
} void Query(int root,int deep,ll x)
{
if(deep == -)
{
printf("%d\n",node[root].val);
return;
} if((x & a[deep]) && node[root].l != - || node[root].r == -)
{
Query(node[root].l,deep-,x);
}
else
{
Query(node[root].r,deep-,x);
}
} int main()
{
int T,cnt=;
scanf("%d",&T);
a[]=;
for(int i=;i<N;i++) a[i]=a[i-]<<; while(T--)
{
printf("Case #%d:\n",cnt++);
root = -,p=;
scanf("%d%d",&n,&m);
for(int i=;i<=n;i++)
{
int t;
scanf("%d",&t);
Insert(root,N,(ll)t);
} while(m--)
{
int t;
scanf("%d",&t);
Query(,N,(ll)t);
}
}
}
C题,和B题很像,题意是任意从字典树中取出两个元素,求它们的和和剩下的元素中任意一个进行异或,得到的最大的答案是多少。一开始看了一个人的博客,那个人写的很烦,以为是难题,但是后来参考了另外一个人的代码,发现就是B题。思路很简单,枚举取出的元素,然后将这两个元素从字典树中删除!然后进行匹配,匹配完以后加回去即可。这样,删除操作其实就是Insert操作中,把cnt+1改成-1即可,所以可以把Insert改成update就可以实现两个功能了,具体见代码:
#include <stdio.h>
#include <algorithm>
#include <string.h>
using namespace std;
typedef long long ll; const int N = ;
int num[+]; struct node
{
int val;
int cnt;
node *child[];
node()
{
cnt=;
val = -;
for(int i = ; i < ; i ++)
{
child[i] = NULL;
}
}
}; node *root; void Freedom(node *p)
{
for(int i= ; i < ; i ++)
{
if(p->child[i] != NULL) Freedom(p->child[i]);
} delete p;
} void update(node *p,int deep,ll x,int op)
{
if(deep == -) {p->val = x;return;} int m = ( (x>>deep) & );
if(p->child[m] == NULL)
{
node *newnode = new node();
p->child[m] = newnode;
} p->child[m]->cnt += op;
update(p->child[m],deep-,x,op);
} int Query(node *p,int deep,ll x)
{
if(deep == -) return (int)p->val; int m = - ( (x>>deep) & ) ;
if(p->child[m] != NULL && p->child[m]->cnt != )
{
return Query(p->child[m],deep-,x);
}
else
{
return Query(p->child[-m],deep-,x);
}
} int main()
{
int T,cnt=;
scanf("%d",&T); while(T--)
{
root = new node(); int n;
scanf("%d",&n);
for(int i=;i<=n;i++)
{
scanf("%d",num+i);
update(root,N,(ll)num[i],);
} int ans=;
for(int i=;i<=n;i++)
{
for(int j=i+;j<=n;j++)
{
int t1 = num[i] + num[j]; update(root,N,num[i],-); //从字典树里删除这两个数
update(root,N,num[j],-); int t2 = Query(root,N,(ll)t1); //查找最大的值 ans = max(ans,t1^t2); update(root,N,num[i],); //将这两个数再次放回字典树里面
update(root,N,num[j],); }
} printf("%d\n",ans); Freedom(root); //释放内存
}
}
同时,从这题中,还有一个对new出来的节点进行delete的操作,要释放内存。所以以后干脆就不要写new的字典树写法好了。。
D题,给出一堆的单词,找出每个单词的最短缩写,要求是每个缩写必须能认出这个单词,打个比方,app和apple,那么app作为app的缩写以后,apple的缩写就必须为appl。那么思路就很简单了:对一个单词,不断地匹配,直到一个节点,这个节点的cnt值为1或者这个点已经是这个单词的最后一个字母了,那么到此为止了。可以从上面这两个单词中很好的理解。虽然这题是我独立做出来的,但是查询操作还是写麻烦了,其实不需要用队列来装,只要该节点的cnt值大于1,就输出这个字母即可,直到为1或者单词结束为止。具体见代码:
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <queue>
using namespace std;
typedef long long ll; int num[+];
char s[+][]; struct node
{
int cnt;
node *child[];
node()
{
cnt=;
for(int i = ; i < ; i ++)
{
child[i] = NULL;
}
}
}; node *root; void Freedom(node *p)
{
for(int i= ; i < ; i ++)
{
if(p->child[i] != NULL) Freedom(p->child[i]);
} delete p;
} void Insert(char *s)
{
node *p = root;
for(int i=;s[i];i++)
{
int m = s[i] - 'a';
if(p->child[m] == NULL)
{
p->child[m] = new node();
}
p = p->child[m];
p->cnt++;
}
} void Query(char *s)
{
node *p = root;
queue<char> Q;
for(int i=;s[i];i++)
{
int m = s[i] - 'a';
if(p->child[m]->cnt == )
{
while(!Q.empty())
{
char c = Q.front();Q.pop();
putchar(c);
}
printf("%c\n",s[i]);
return;
}
else
{
Q.push(s[i]);
}
p = p->child[m];
} while(!Q.empty())
{
char c = Q.front();Q.pop();
putchar(c);
}
puts("");
} int main()
{
int cnt = ;
root = new node();
while(gets(s[cnt++]) != NULL)
{
Insert(s[cnt-]);
//if(cnt==12) break;
} for(int i=;i<cnt;i++)
{
printf("%s ",s[i]);
Query(s[i]);
}
}
E题的意思,给出一堆的字符串,如果有一个字符串是另外一个的前缀,那么输出NO,否则输出YES。思路和上题类似,枚举所有字符串,一直到结尾为止,如果结尾那个字母出现的次数大于1,那么这个字符串肯定是另外一个字符串的前缀了。具体见代码:
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <queue>
using namespace std;
typedef long long ll; int n,tot=;
char s[+][]; struct node
{
int cnt;
node *child[];
void clear()
{
cnt=;
for(int i = ; i < ; i ++)
{
child[i] = NULL;
}
}
}N[+]; node *root; node *newnode()
{
node *p = &N[tot++];
p->clear();
return p;
} void Freedom(node *p)
{
for(int i= ; i < ; i ++)
{
if(p->child[i] != NULL) Freedom(p->child[i]);
} delete p;
} void Insert(char *s)
{
node *p = root;
for(int i=;s[i];i++)
{
int m = s[i] - '';
if(p->child[m] == NULL)
{
p->child[m] = newnode();
}
p = p->child[m];
p->cnt++;
}
} bool Isok(char *s)
{
node *p = root;
for(int i=;s[i];i++)
{
int m = s[i] - '';
p = p->child[m];
}
if(p->cnt == ) return true;
else return false;
} int main()
{
int T;
scanf("%d",&T);
while(T--)
{
tot=;
root = newnode();
scanf("%d",&n);
for(int i=;i<=n;i++)
{
scanf("%s",s[i]);
Insert(s[i]);
} int flag=;
for(int i=;i<=n;i++)
{
if(!Isok(s[i])) {flag=;break;}
}
if(!flag) puts("NO");
else puts("YES");
}
}
F题,意思是找出字典中的所有串,这个串是由字典中的另外两个串拼成的。第一次做时,n^2拼接然后查找,结果当然是TLE,GG。。然后用了大力的方法:比方说三个单词,abc,qwe,abcqwe。查找abcqwe的时候,到c时,c是某个单词(abc)的结尾并且c的下一个字母并不是abcqwe的结尾,让下一个字母(q)入栈,到时候取出栈内所有元素,然后对每个元素进行继续匹配,如果到最后一个字母时这个字母是另外一个单词的结尾,那么这个单词(abcqwe)就是满足题意的。。这题还是有一点技巧性的,比方说栈内存的是那个字母的位置而不是char。关于判断是不是另外单词的结尾,只要在节点处在设置一个布尔类型的变量ised即可。具体见代码吧:
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <queue>
#include <stack>
using namespace std;
typedef long long ll; int n,tot=;
char s[+][]; struct node
{
int cnt;
bool ised;
node *child[];
void clear()
{
cnt = ;
ised = false;
for(int i = ; i < ; i ++)
{
child[i] = NULL;
}
}
}N[*(+)]; node *root; node *newnode()
{
node *p = &N[tot++];
p->clear();
return p;
} void Freedom(node *p)
{
for(int i= ; i < ; i ++)
{
if(p->child[i] != NULL) Freedom(p->child[i]);
} delete p;
} void Insert(char *s)
{
node *p = root;
for(int i=;s[i];i++)
{
int m = s[i] - 'a';
if(p->child[m] == NULL)
{
p->child[m] = newnode();
}
p = p->child[m];
p->cnt++;
}
p->ised = true;
} bool Query(char *s)
{
stack<int> S;
node *p = root;
for(int i=;s[i];i++)
{
int m = s[i] - 'a';
p = p->child[m];
if(p->ised == true && s[i+]) S.push(i+);
} while(!S.empty())
{
p = root;
int x = S.top();S.pop();
for(int i=x;s[i];i++)
{
int m = s[i] - 'a';
if(p->child[m] == NULL) break;
else p = p->child[m];
if(!s[i+] && p->ised == true) return true;
}
}
return false;
} int main()
{
int cnt = ;
tot = ;
root = newnode();
while(gets(s[cnt++]) != NULL)
{
Insert(s[cnt-]);
//if(cnt == 6) break;
} for(int i=;i<cnt;i++)
{
if(Query(s[i])) puts(s[i]);
}
}
最后一题也是很吊的一题。题意是模仿手机上的九格输入法,输入了一系列的单词。然后给你一些数字,表示现在的输入,问每次输入一个数字,按照频率,最可能是在输入什么内容。。具体的题意还是看原题吧。然后思路就是同样的匹配,把每一个数字当做一个状态,对这个状态进行若干个方向的选择,选择频率最大的内容即可。具体见代码吧:
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <queue>
#include <stack>
#include <string>
#include<iostream>
using namespace std;
typedef long long ll; int n,tot=,fre;
string ans;
char str[+];
int cnt[] = {,,,,,,,,,};
char ch[][] = {"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"}; struct node
{
int cnt;
node *child[];
void clear()
{
cnt = ;
for(int i = ; i < ; i ++)
{
child[i] = NULL;
}
}
}N[+]; node *root; node *newnode()
{
node *p = &N[tot++];
p->clear();
return p;
} void Freedom(node *p)
{
for(int i= ; i < ; i ++)
{
if(p->child[i] != NULL) Freedom(p->child[i]);
} delete p;
} void Insert(char *s,int t)
{
node *p = root;
for(int i=;s[i];i++)
{
int m = s[i] - 'a';
if(p->child[m] == NULL)
{
p->child[m] = newnode();
}
p = p->child[m];
p->cnt += t;
}
} void Query(int st,int ed,node *p,string s)
{
if(st == ed)
{
if(p->cnt > fre)
{
fre = p->cnt;
ans = s;
}
} int num = str[st] - '';
for(int i=;i<cnt[num];i++)
{
//对这个键的各个方向进行试探性的选择
int m = ch[num][i] - 'a';
if(p->child[m] != NULL) Query(st+,ed,p->child[m],s+ch[num][i]);
}
} int main()
{
int T;
scanf("%d",&T);
for(int kase=;kase<=T;kase++)
{
tot = ;
root = newnode();
printf("Scenario #%d:\n",kase);
int n;
scanf("%d",&n);
for(int i=;i<=n;i++)
{
char s[+];
int t;
scanf("%s%d",s,&t);
Insert(s,t);
} scanf("%d",&n);
while(n--)
{
scanf("%s",str);
int len = strlen(str);
for(int i=;i<len;i++)
{
fre = ;
Query(,i,root,"");
if(fre) cout << ans << endl;
else puts("MANUALLY");
}
puts("");
}
puts("");
}
}
那么,字典树这个专题大概是完了,依稀记得上次百度之星有一道是字典树的没做,lyf给了一个很好的板子,下次有空了再补上。= =!
ACM之路(15)—— 字典树入门练习的更多相关文章
- HDU 1251 统计难题(字典树入门模板题 很重要)
统计难题 Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 131070/65535 K (Java/Others)Total Submi ...
- hdu 1251 统计难题 (字典树入门题)
/******************************************************* 题目: 统计难题 (hdu 1251) 链接: http://acm.hdu.edu. ...
- HDU 4825 Xor Sum(01字典树入门题)
http://acm.hdu.edu.cn/showproblem.php?pid=4825 题意: 给出一些数,然后给出多个询问,每个询问要从之前给出的数中选择异或起来后值最大的数. 思路:将给出的 ...
- HDU 5687 字典树入门
Problem C Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 131072/131072 K (Java/Others)Total ...
- hdu 1247 (字典树入门)
Hat’s Words Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total ...
- hdu2222Keywords Search字典树入门……
#include<iostream> #include<cstring> using namespace std; struct node { int num; node *n ...
- [ACM] hdu 1251 统计难题 (字典树)
统计难题 Problem Description Ignatius近期遇到一个难题,老师交给他非常多单词(仅仅有小写字母组成,不会有反复的单词出现),如今老师要他统计出以某个字符串为前缀的单词数量(单 ...
- HDU 1251 统计难题(字典树模板题)
http://acm.hdu.edu.cn/showproblem.php?pid=1251 题意:给出一些单词,然后有多次询问,每次输出以该单词为前缀的单词的数量. 思路: 字典树入门题. #inc ...
- 数据结构~trie树(字典树)
1.概述 Trie树,又称字典树,单词查找树或者前缀树,是一种用于快速检索的多叉树结构,如英文字母的字典树是一个26叉树,数字的字典树是一个10叉树. 我理解字典树是看了这位大佬博客.还不了解字典树的 ...
随机推荐
- 怎样快捷获取网页的window对象
使用document.defaultView; document.defaultView === window 注意: 1. 如果当前文档不属于window对象, 则返回null; 2. docume ...
- java lesson10homework
1. 输入三个整数x, y, z,请把这三个数由小到大输出. 2. package Homework10; 3. //:类方法 4. public class Sort { 5. voi ...
- JavaScript实现按照指定长度为数字前面补零输出的方法
本文实例讲述了JavaScript实现按照指定长度为数字前面补零输出的方法.分享给大家供大家参考.具体分析如下: 例如我们希望输出的数字长度是固定的,假设为10,如果数字为123,则输出0000000 ...
- 小程序 wxs时间戳转字符串
function formatDate(value) { //不能使用 new Date() var time = getDate(value); var year = time.getFullYea ...
- js实现复制 、剪切功能-clipboard.min.js 示例
html: <div id="txt">我是要复制的内容</button> <button id="copyBtn">点击复 ...
- VM安装vmtools后centos7无法上网
先安装VmTools工具 文件 /etc/sysconfig/network-scripts/ifcfg-ens33(这里的enp0s3不是固定的,看你具体情况,但是基本是en开头的) 将 ONBOO ...
- 简单介绍 Java 构造器
导读 构造器是编程的强大组件.使用它们来释放 Java 的全部潜力. 在开源.跨平台编程领域,Java 无疑(?)是无可争议的重量级语言.尽管有许多伟大的跨平台框架,但很少有像 Java 那样统一和直 ...
- redis——java访问redis
1,使用jedis的java客户端来访问redis服务器,有点类似于通过jdbc访问mysql一样: 2,如果是spring集成时,可以使用spring data 来访问redis,spring da ...
- Python之datetime模块
datatime模块重新封装了time模块,提供更多接口,提供的类有:date,time,datetime,timedelta,tzinfo. 1.date类 datetime.date(year, ...
- systemctl可以实现nginx进程挂了之后自动重新启动
接 2018年7月31日的那篇: vim /lib/systemd/system/nginx.service [Service]Restart=alwaysRestartSec=1Type=forki ...