目录
1.Trie字符串统计
1.1基本介绍
1.2主要性质
1.根节点不包含字符,除根节点外的每一个子节点都包含一个字符。2.从根节点到某一个节点,路径上经过的字符连接起来,为该节点对应的字符串。3.每个节点的所有子节点包含的字符互不相同。
图例:
1.3 典例
维护一个字符串集合,支持两种操作:
I x
向集合中插入一个字符串 xx;Q x
询问一个字符串在集合中出现了多少次。共有 N 个操作,所有输入的字符串总长度不超过 10^5,字符串仅包含小写英文字母。
输入格式
第一行包含整数 N,表示操作数。
接下来 N 行,每行包含一个操作指令,指令为
I x
或Q x
中的一种。输出格式
对于每个询问指令
Q x
,都要输出一个整数作为结果,表示 xx 在集合中出现的次数。每个结果占一行。
1.3.1实现思路
2.插入操作:每次插入一个字符串,循环处 理每一个字符。利用 u=str[i]-'a' 将每个字符转化为整数操作,判断 son[p][u] 的值是否为0。若为0,不存在该字符,就插入该字符,
即令 son[p][u]=++idx ,然后下移到子结点继续插入字符串的下一个字符;若不为 0,就表示之前已经插入过该字符,直接下移。最后该字符串插入结束。最后p就是该字符串结尾的坐标。
3.查询操作:类似插入操作进行判断,若出现某一次 s[p][u] 为0就此结束,意味着不存在该字符串,直到循环 结束,存在该字符串,返回计数数组cnt[p]。
1.3.2模板
1.3.2.1insert函数
private static void insert(String str) {
int p = 0;//类似指针,指向当前节点
for (int i = 0; i < str.length(); i++) {
int u = str.charAt(i) - 'a';//将字母转化为数字
if (son[p][u] == 0) {
//该节点不存在,创建节点,其值为下一个节点位置
son[p][u] = ++idx;
}
p = son[p][u]; //使“p指针”指向下一个节点位置
}
cnt[p]++;//结束时的标记,也是记录以此节点结束的字符串个数
}
1.3.2.2query函数
private static int query(String str) {
int p = 0;
for (int i = 0; i < str.length(); i++) {
int u = str.charAt(i) - 'a';
if (son[p][u] == 0) {
//该节点不存在,即该字符串不存在
return 0;
}
p = son[p][u];
}
return cnt[p];
}
1.3.3完整代码
import java.util.Scanner;
public class Main {
/*维护一个字符串集合,支持两种操作:
I x 向集合中插入一个字符串 x;
Q x 询问一个字符串在集合中出现了多少次。
共有 N个操作,所有输入的字符串总长度不超过 1e5,字符串仅包含小写英文字母。*/
/*输入格式
第一行包含整数 N,表示操作数。
接下来 N行,每行包含一个操作指令,指令为 I x 或 Q x 中的一种。*/
static int N = 100010, idx;
static int[][] son = new int[N][26];
static int[] cnt = new int[N];
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int q = in.nextInt();
in.nextLine();
while (q-- > 0) {
String[] s = in.nextLine().split(" ");
if (s[0].equals("I")) {
insert(s[1]);
} else {
System.out.println(query(s[1]));
}
}
}
private static int query(String str) {
int p = 0;
for (int i = 0; i < str.length(); i++) {
int u = str.charAt(i) - 'a';
if (son[p][u] == 0) {
//该节点不存在,即该字符串不存在
return 0;
}
p = son[p][u];
}
return cnt[p];
}
private static void insert(String str) {
int p = 0;//类似指针,指向当前节点
for (int i = 0; i < str.length(); i++) {
int u = str.charAt(i) - 'a';//将字母转化为数字
if (son[p][u] == 0) {
//该节点不存在,创建节点,其值为下一个节点位置
son[p][u] = ++idx;
}
p = son[p][u]; //使“p指针”指向下一个节点位置
}
cnt[p]++;//结束时的标记,也是记录以此节点结束的字符串个数
}
}
2.变式:大按钮
来源:Google Kickstart2018 Round H Problem A
2.1题目
2.2思路
1.使用Trie树,把所有的禁用的字符串P,都insert到Trie树中,不需要判断前缀,只需要给加进去的P字符串的末尾加一个标记(is_end)即可。
2.d写一个dfs()函数用来计算出需要减掉多少个方案,直接对Trie树进行dfs就行。
2.3代码
import java.util.Arrays;
import java.util.Scanner;
public class Main {
static int N = 5010, n, idx;
static int[][] son = new int[N][2];
static boolean[] is_end = new boolean[N];
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int T = in.nextInt();
for (int cases = 1; cases <= T; cases++) {
for (int i = 0; i < N; i++) {
Arrays.fill(son[i], 0);
}
Arrays.fill(is_end, false);
idx = 0;
n = in.nextInt();
int p = in.nextInt();
for (int i = 0; i < p; i++) {
String str = in.next();
insert(str);
}
System.out.printf("Case #%d: %d\n", cases, (1L << n) - dfs(0, 0));
}
}
private static long dfs(int p, int s) {
if (is_end[p]) {
return 1L << (n - s);
}
long res = 0;
for (int i = 0; i < 2; i++) {
if (son[p][i] != 0) {
res += dfs(son[p][i], s + 1);
}
}
return res;
}
private static void insert(String str) {
int p = 0;
for (int i = 0; i < str.length(); i++) {
int u = str.charAt(i) == 'B' ? 0 : 1;
if (son[p][u] == 0) {
son[p][u] = ++idx;
}
p = son[p][u];
}
is_end[p] = true;
}
}
以上为我个人的小分享,如有问题,欢迎讨论!!!
都看到这了,不如关注一下,给个免费的赞