字典树都是跟的这个博客学的→:https://blog.csdn.net/qq_38891827/article/details/80532462
这些题目也都是他里面的题目,就是把题目按难度排了个序 + 自己整理了下思路(代码也差不多
主要是为了记录一下 忘了的话以后可以翻翻看hhh
模板
const int maxn = 1e5 + 10; int Next[maxn][26]; bool flag[maxn]; int tol; void Insert(char *s) { int len = strlen(s); int root = 0; for (int i = 0; i < len; i++) { int id = s[i] - 'a'; if (!Next[root][id]) tree[root][id] = ++tot; root = Next[root][id]; } flag[root] = true; } bool Find(char *s) { int len = strlen(s); int root = 0; for (int i = 0; i < len; i++) { int id = s[i] - 'a'; if (!Next[root][id]) return false; root = Next[root][id]; } return true; }
maxn这个要开多大得看具体题目 有的题目非2e6不可 有的1e5都会爆 所以很绝望...
第一道题:单词数HDU - 2072
模板题咯 边插入边统计答案即可
#include <bits/stdc++.h> using namespace std; const int maxn = 1e5 + 10; int Next[maxn][26], cntword[maxn], tol, ans; void Insert(string s) { int len = s.size(); int root = 0; for (int i = 0; i < len; i++) { int id = s[i] - 'a'; if (!Next[root][id]) Next[root][id] = ++tol; root = Next[root][id]; } if (!cntword[root]) ans++; cntword[root] = 1; } string s; int main() { while (getline(cin, s)) { ans = 0; memset(Next, 0, sizeof(Next)); memset(cntword, 0, sizeof(cntword)); tol = 0; if (s[0] == '#') return 0; stringstream ss(s); string s2; while (ss >> s2) { Insert(s2); } cout << ans << '\n'; } return 0; }
第二道题:统计难题HDU-1251
这道题就把flag变一下 表示成每一个单词里面每个字母出现的次数
对每一个字符串进行Find操作 如果到哪个位置没字母了 答案就是0 因为根据题意 后面给的字符串得作为前面插入的字符串的前缀
所以这个Find操作必须搜到这个字符串的结尾 所以中途无法继续下去就直接返回0
找到最后返回flag[root]就行了(代码中用的是cntword
#include <bits/stdc++.h> using namespace std; const int maxn = 1e6 + 10; int Next[maxn][26]; int cntword[maxn]; int tol; void Insert(char *s) { int len = strlen(s); int root = 0; for (int i = 0; i < len; i++) { int id = s[i] - 'a'; if (!Next[root][id]) Next[root][id] = ++tol; root = Next[root][id]; cntword[root]++; } } int query(char *s) { int root = 0; int len = strlen(s); for (int i = 0; i < len; i++) { int id = s[i] - 'a'; if (!Next[root][id]) return 0; root = Next[root][id]; } return cntword[root]; } char s[maxn]; int main() { while (gets(s)) { if (s[0] == '\0') break; Insert(s); } while (~scanf("%s", s)) { printf("%d\n", query(s)); } }
题意就是给一些电话号码 不能存在某个是另一个的前缀
先插入 后check一下就好了 注意变为id = s[i] - '0'
#include <cstdio> #include <algorithm> #include <cstring> using namespace std; const int maxn = 5e5 + 10; int Next[maxn][11], flag[maxn]; int tol; bool ans; void Insert(char *s) { int len = strlen(s); int root = 0; for (int i = 0; i < len; i++) { int id = s[i] - '0'; if (!Next[root][id]) Next[root][id] = ++tol; root = Next[root][id]; } flag[root]++; } void check(char *s) { int len = strlen(s) - 1; int root = 0; for (int i = 0; i < len; i++) { int id = s[i] - '0'; root = Next[root][id]; if (flag[root]) { ans = true; break; } } } char s[maxn][20]; int main() { int T; scanf("%d", &T); while (T--) { ans = false; memset(Next, 0, sizeof(Next)); memset(flag, 0, sizeof(flag)); int n; tol = 0; scanf("%d", &n); for (int i = 0; i < n; i++) { scanf("%s", s[i]); Insert(s[i]); } for (int i = 0; i < n; i++) { check(s[i]); if (ans) break; } if (ans) puts("NO"); else puts("YES"); } return 0; }
第四题: Shortest Prefixes POJ - 2001
题意是给一堆字符串 然后找出每个字符串的最短前缀 这个最短前缀必须是只有他自己有的
先插入 记录一下每个单词每个字母出现的次数 到了flag[root]==1的时候就可以返回了 因为flag[root]==1就表示这个单词到这个位置只有一个字符串走过(也就是它本身
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int maxn = 2e5 + 10; int Next[maxn][26], tol, flag[maxn]; char s[1100][25]; void Insert(char *s) { int root = 0; int len = strlen(s); for (int i = 0; i < len; i++) { int id = s[i] - 'a'; if (!Next[root][id]) Next[root][id] = ++tol; root = Next[root][id]; flag[root]++; } } void Find(char *s, char *ans) { int len = strlen(s); int root = 0; int cnt = 0; for (int i = 0; i < len; i++) { int id = s[i] - 'a'; ans[cnt++] = s[i]; root = Next[root][id]; if (flag[root] == 1) return; } } int main() { int cnt = 0; while (~scanf("%s", s[cnt++])) { Insert(s[cnt-1]); } for (int i = 0; i < cnt; i++) { char ans[1005] = {0}; Find(s[i], ans); printf("%s %s\n", s[i], ans); } return 0; }
第五题:Concatenation of Languages UVA - 10887
题意是给n,m个字符串 然后n个m个相接(s1接s2就行了不需要s2接s1
求有多少不同的字符串
n m最多有1500 直接暴力接一遍再插入 顺便统计答案就好了
注意这n+m个字符串有可能为空 以及拼接的问题
#include <cstdio> #include <algorithm> #include <cstring> using namespace std; const int maxn = 2e6 + 10; int Next[maxn][26]; int tol, ans; bool flag[maxn]; char s1[1510][50], s2[1510][50]; void init() { memset(flag, 0, sizeof(flag)); memset(Next, 0, sizeof(Next)); memset(s1, 0, sizeof(s1)); memset(s2, 0, sizeof(s2)); tol = ans = 0; } void Insert(char *s) { int len = strlen(s); int root = 0; for (int i = 0; i < len; i++) { int id = s[i] - 'a'; if (!Next[root][id]) Next[root][id] = ++tol; root = Next[root][id]; } if (!flag[root]) ans++; flag[root] = 1; } int main() { int T; int kase = 0; scanf("%d", &T); while (T--) { init(); int n, m; scanf("%d%d", &n, &m); getchar(); for (int i = 0; i < n; i++) gets(s1[i]); for (int i = 0; i < m; i++) gets(s2[i]); for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { char s3[30] = {0}; strcat(s3, s1[i]); strcat(s3, s2[j]); Insert(s3); } } printf("Case %d: %d\n", ++kase, ans); } return 0; }
第六题:What Are You Talking About HDU - 1075
题意就是两种语言存在一一对应关系 然后给一个火星文文本 如果里面的单词可以映射就打印映射后的单词
如果不行就输出原单词
map应该可以水过吧
字典树的做法就是把火星文插入字典树里面 并且把最后一个root给返回作为map的下标 映射的值就是对应的字符在那堆字符里面的下标
然后输入的火星文就去字典树里面找一下 看能不能找到 然后输出就好了
#include <bits/stdc++.h> using namespace std; const int maxn = 2e6 + 10; int Next[maxn][26]; bool flag[maxn]; int tol; int Insert(char *s) { int len = strlen(s); int root = 0; for (int i = 0; i < len; i++) { int id = s[i] - 'a'; if (!Next[root][id]) Next[root][id] = ++tol; root = Next[root][id]; } flag[root] = true; return root; } int Find(char *s) { int len = strlen(s); int root = 0; for (int i = 0; i < len; i++) { int id = s[i] - 'a'; if (!Next[root][id]) return -1; root = Next[root][id]; } if (flag[root]) return root; return -1; } char s[maxn]; char chinese[maxn][20]; int mp[maxn]; int main() { scanf("%s", s); int cnt = 0; while (scanf("%s", chinese[cnt]) == 1) { if (chinese[cnt][0] == 'E') { scanf("%s", s); break; } scanf("%s", s); mp[Insert(s)] = cnt; cnt++; } getchar(); while (gets(s) != NULL) { if (s[0] == 'E') break; int len = strlen(s); char s2[15]; for (int i = 0; i < len; i++) { if (s[i] >= 'a' && s[i] <= 'z' && i < len) { cnt = 0; while (s[i] >= 'a' && s[i] <= 'z') { s2[cnt++] = s[i++]; } s2[cnt] = 0; int p = Find(s2); if (p != -1) printf("%s", chinese[mp[p]]); else printf("%s", s2); printf("%c", s[i]); } else { printf("%c", s[i]); } } puts(""); } return 0; }
第七题: DNA Prefix LightOJ - 1224
题意求 相同的前缀的长度*字符串数最大
边插入边求解 ans = max(ans, flag[root] * i)
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int maxn = 2e6 + 10; int Next[maxn][5], flag[maxn]; int tol; int ans; void Insert(char *s) { int len = strlen(s); int root = 0; for (int i = 0; i < len; i++) { int id = 0; if (s[i] == 'A') id = 0; else if (s[i] == 'T') id = 1; else if (s[i] == 'G') id = 2; else id = 3; if (!Next[root][id]) Next[root][id] = ++tol; root = Next[root][id]; flag[root]++; ans = max(ans, (i + 1) * flag[root]); } } char s[55]; int main() { int T; int kase = 0; scanf("%d", &T); while (T--) { int n; scanf("%d", &n); tol = ans = 0; memset(Next, 0, sizeof(Next)); memset(flag, 0, sizeof(flag)); while (n--) { scanf("%s", s); Insert(s); } printf("Case %d: ", ++kase); printf("%d\n", ans); } return 0; }
题意就是问给定的字符串能否头尾相接连成一条直线
有两个问题:一、连成一条 二 、头尾相接
第一个问题就是图的连通性 用并查集解决
第二个问题就是就像欧拉图 仅有两个度为奇数或者没有也行
#include <cstdio> #include <cstring> #include <algorithm> #include <set> using namespace std; const int maxn = 2e6 + 10; int Next[maxn][26], flag[maxn], vis[maxn], par[maxn]; int tol, lisan; set<int> st; int Insert(char *s) { int len = strlen(s); int root = 0; for (int i = 0; i < len; i++) { int id = s[i] - 'a'; if (!Next[root][id]) Next[root][id] = ++tol; root = Next[root][id]; } if (!vis[root]) vis[root] = ++lisan; if (par[lisan] == 0) par[lisan] = lisan; return vis[root]; } int Find(int x) { return par[x] == x ? x : par[x] = Find(par[x]); } void unit(int x, int y) { x = Find(x), y = Find(y); if (x != y) { par[x] = y; } } char s1[20], s2[20]; int main() { while(~scanf("%s%s", s1, s2)) { int x = Insert(s1), y = Insert(s2); flag[x]++; flag[y]++; unit(x, y); } int r1 = 0, r2 = 0; for (int i = 1; i <= lisan; i++) { if (Find(i) != Find(1)) { puts("Impossible"); return 0; } if (flag[i] & 1) r1++; } if (r1 == 0 || r1 == 2) puts("Possible"); else puts("Impossible"); return 0; }
题意 问有没有一个单词可由另外两个单词连接形成
建两棵字典树 一棵正序插入 一棵逆序插入
查询的时候就正序查一遍 倒叙查一遍 如果有flag就对对应位置上的标记
最后遍历一遍vis数组就知道有没有了
#include <cstdio> #include <algorithm> #include <cstring> #include <string> #include <sstream> #include <set> using namespace std; const int maxn = 2e6 + 10; int Next1[maxn][26], Next2[maxn][26], vis[55]; bool flag1[maxn], flag2[maxn]; int tol1, tol2; set<string> ans; void Reverse(char s[], int len) { int i = 0, j = len - 1; while (i <= j) { swap(s[i], s[j]); ++i, --j; } } void Insert1(char *s) { int len = strlen(s); int root = 0; for (int i = 0; i < len; i++) { int id = s[i] - 'a'; if (!Next1[root][id]) Next1[root][id] = ++tol1; root = Next1[root][id]; } flag1[root] = true; } void Insert2(char *s) { int len = strlen(s); int root = 0; for (int i = 0; i < len; i++) { int id = s[i] - 'a'; if (!Next2[root][id]) Next2[root][id] = ++tol2; root = Next2[root][id]; } flag2[root] = true; } void Find1(char *s) { int len = strlen(s); int root = 0; for (int i = 0; i < len - 1; i++) { int id = s[i] - 'a'; root = Next1[root][id]; if (flag1[root]) vis[i]++; } } void Find2(char *s) { int len = strlen(s); int root = 0; for (int i = 0; i < len - 1; i++) { int id = s[i] - 'a'; root = Next2[root][id]; if (flag2[root]) vis[len-2-i]++; } } char s[(int)5e4 + 10][55]; int main() { int cnt = 0; while (scanf("%s", s[cnt++]) != EOF) { Insert1(s[cnt-1]); int len = strlen(s[cnt-1]); Reverse(s[cnt-1], len); // strrev(s[cnt-1]); Insert2(s[cnt-1]); Reverse(s[cnt-1], len); // strrev(s[cnt-1]); } for (int i = 0; i < cnt; i++) { memset(vis, 0, sizeof(vis)); int len = strlen(s[i]); Find1(s[i]); // strrev(s[i]); Reverse(s[i], len); Find2(s[i]); // strrev(s[i]); Reverse(s[i], len); for (int j = 0; j < len; j++) { if (vis[j] == 2) { string temp = s[i]; ans.insert(temp); break; } } } set<string>::iterator it; for (it = ans.begin(); it != ans.end(); it++) { printf("%s\n", (*it).c_str()); } return 0; }
第十题:J - Anagram Groups POJ - 2408
题意就是把所有单词都按字典序重新排一遍 把这些单词组(一个组就是他们重排完事一个单词)从大到小排序(大小指这个组有多少个字符串)输出
一样大小就按字典序输出
首先是答案怎么存储 因为得把原来的字符串按字典序输出 而且一样的只输出一遍 就是一个set 再用一个cnt记数 就一个struct就好了
然后就很简单了 每次插入的时候先把这个字符串排下序 然后插入 最后看看存没存在 给对应的结构体数组去插入原字符串以及计数器+1就好了
#include <cstdio> #include <cstring> #include <algorithm> #include <string> #include <set> #include <sstream> using namespace std; const int maxn = 1e6 + 10; struct ANS { int cnt; set<string> st; } ans[maxn]; int Next[maxn][26], tol, vis[maxn]; int cnt; bool cmp(const ANS& a, const ANS& b) { if (a.cnt == b.cnt) return *(a.st.begin()) < *(b.st.begin()); return a.cnt > b.cnt; } void Insert(char *s) { int len = strlen(s); string ss = s; sort(ss.begin(), ss.end()); int root = 0; for (int i = 0; i < len; i++) { int id = ss[i] - 'a'; if (!Next[root][id]) Next[root][id] = ++tol; root = Next[root][id]; } if (!vis[root]) vis[root] = ++cnt; ans[vis[root]].cnt++; string temp = s; ans[vis[root]].st.insert(temp); } char s[maxn]; int main() { while (~scanf("%s", s)) { Insert(s); } sort(ans + 1, ans + 1 + cnt, cmp); for (int i = 1; i <= 5; i++) { printf("Group of size %d: ", ans[i].cnt); set<string>::iterator it; for (it = ans[i].st.begin(); it != ans[i].st.end(); it++) { printf("%s ", (*it).c_str()); } puts("."); } return 0; }
还有三道比较难的题 过后再写...留坑...