字典树,01字典树,可持续化01字典树(总结+例题)

目录

字典树

01字典树

        字典树例题:

power oj 2390: 查单词

HDU 1671 Phone List

HDU 1004Let the Balloon Rise

HDU 1075 What Are You Talking About

         HDU 4287 Intelligent IME

         HDU 1247 Hat’s Words

01字典树例题

HDU 4825 Xor Sum

HDU 5536 Chip Factory

HDU 5269 ZYB loves Xor I

CodeForces - 842D Vitya and Strange Lesson

        POJ 3764 The xor-longest Path 

可持续化01字典树例题:

HDU 4757 Tree

HDU-6191 Query on A Tree


字典树

学了字典树有一段时间了,觉得字典树在我理解下就是一颗26叉树(引用在数字上的字典顺)和52叉树(应用在字母上面)。

字典树的优点是极大的减少了很多无谓的字符串的比较,这样时查找的效率就比较高,这也是相当于用空间来换时间。

时间复杂度:字典树的插入和查找都是O(n)的复杂度(n为字符串的长度)。

字典树分为静态和动态:静态和动态其实就是在开辟数组上有区别,动态是用的时候需要开才开,静态是提前开好(建议大家使用静态的,因为开动态数组的时候,会浪费时间,程序结束后要释放空间,在很多题当中,这样的复杂度是不允许的)。

首先我们应该清楚的是,字典树的根节点不包含字符,除根节点外每一个节点都只包含一个字符。 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串,且每个节点的所有子节点包含的字符都不相同。

下面介绍一下怎么构建一颗字典树吧(我这里就只介绍静态字典树)。

 首先一颗字典树不需要建树,是直接执行插入和查找操作,它是在插入过程中就直接建树。

就以上图为例:上图字典树一共插入了五个单词,分别是:abcd,abd,bcd,efg,hij。

首先我们要申明我们使用的数组,后面这个30如果是数字可以为10,只有大写或小写字母这个数可以是26,这个根据题中要求来定,且数组初值都为0(方便后期判断数组表示的节点在之前是否被插入)。

int tree[maxn][30],tot;

我先把通常的插入操作代码发出来

void Insert(char *str){
    int len=strlen(str);
    int root=0;///通常根节点我们设置为0
    for(int i=0;i<len;i++){
        int id=str[i]-'a';///将字母转化成对应数字,然后插到数组里。
        if(!tree[root][id])///如果当前节点没有对应字母的子节点,我们就要创建一个新的节点。
            tree[root][id]=++tot;///这步相当于创立一个新节点,++tot给对应节点附上一个值,且保证每个节点的值不一样
        root=tree[root][id];///把当前节点的指针移到它的对应字母的子节点上。
    }
    vis[root]=1;///单词的最后标记一下。
}

在插入每个单词时,我们都从根节点开始找,且通常根节点赋值为0。

插入abcd:插入a时:id=0,对应tree[root][id]=0,说明a之前没有被插入过,执行:tree[root][id]=++tot;这时候tree[root][0]=1,最后root=tree[root][id](这步将直接移到对应子节点上,执行完后root=1)。插入b时:id=1,对应tree[root][id]=0,执行:tree[root][id]=++tot;这时候tree[root][1]=1,最后root=tree[root][id](执行完后root=2),插入cd跟ab的情况一下。

插入abd时:插入a时:id=0,对应tree[root][id]=1,说明a之前被插入过,直接执行root=tree[root][id](执行完后root=1)。插入b时:id=1,对应tree[root][id]=1,执行root=tree[root][id](执行完后root=2)。插入d时:id=3,tree[root][id]=0,说明d之前没有被插入,执行:tree[root][id]=++tot;这时候tree[root][id]=5,最后root=tree[root][id]。

插入bcd时:插入b时:id=1,对应tree[root][id]=0(虽然前面有插入过b,但是现在我们是插入一个新单词,是从根节点开始找,因为之前没有插入过以b字母开头的单词,所以我们要重新开辟个节点),执行:tree[root][id]=++tot;最后root=tree[root][id],后面操作就跟前面操作类似了。

后面单词插入操作和前面一样,要注意的是,每插入一个新单词时,都要从根节点开始找当前单词有没有被插入过。

基本上插入操作就是这样,很多时候要根据提醒做一些小小的改变,但是思想是这样的。

查找操作:

bool Find_str(char *str) {
    int root = 0;///查找一个单词有没有还是要从根节点开始查找
    int len = strlen(str);
    for (int i = 0; i < len; i++) {
        int id = str[i] - 'a';///获得对应字母的节点信息
        if (!tree[root][id])///如果节点下面没有该字母对应的字母,说明之前没有插入过该单词
            return false;
        root = tree[root][id];///若是有继续向下找
    }
    if (vis[root])///要是标记数组该位不为0,说明有该单词,不然说明有以这个单词为前缀的单词
        return true;
    return false;
}

既然之前我们已经把所有单词全部插入到字典树里面了,现在我们查找有没有该单词的时候就非常的方便了。

查找有没有某个单词,我们必须还是从根节点开始(因为每个单词我们都是从根节点开始插入的),在查找过程中还是像插入那样子依次向下找,但是与插入不同的是,如果该节点没有值(节点值为初值0),就说明我们之前没有插入该单词,直接返回false,要是能一直能查找下去,最后要判断对应节点是不是一个单词的末尾(vis数组是否为0),要是为0,说明我们之前插入了一个以这个单词为前缀的单词(前缀相同的单词,前缀一定是在一条路线上的),但是并没有插入这个单词。

整体代码:

#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
#include<queue>
#include<set>
#include<cmath>
#include<stack>
#include<string>

const int maxn = 1e6 + 5;
const int mod = 10007;
const int inf = 1e9;
const long long onf = 1e18;
#define me(a, b) memset(a,b,sizeof(a))
#define lowbit(x) x&(-x)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define PI 3.14159265358979323846
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
bool vis[maxn];
int tree[maxn][30], tot;

void Insert(char *str) {
    int len = strlen(str);
    int root = 0;///通常根节点我们设置为0
    for (int i = 0; i < len; i++) {
        int id = str[i] - 'a';///将字母转化成对应数字,然后插到数组里。
        if (!tree[root][id])///如果当前节点没有对应字母的子节点,我们就要创建一个新的节点。
            tree[root][id] = ++tot;///这步相当于创立一个新节点,++tot给对应节点附上一个值,且保证每个节点的值不一样
        root = tree[root][id];///把当前节点的指针移到它的对应字母的子节点上。
    }
    vis[root] = true;///单词的最后标记一下。
}

bool Find_str(char *str) {
    int root = 0;///查找一个单词有没有还是要从根节点开始查找
    int len = strlen(str);
    for (int i = 0; i < len; i++) {
        int id = str[i] - 'a';///获得对应字母的节点信息
        if (!tree[root][id])///如果节点下面没有该字母对应的字母,说明之前没有插入过该单词
            return false;
        root = tree[root][id];///若是有继续向下找
    }
    if (vis[root])///要是标记数组该位不为0,说明有该单词,不然说明有以这个单词为前缀的单词
        return true;
    return false;
}

int main() {
    char str[30];
    while (gets(str) && strlen(str)) {
        Insert(str);
    }
    while (gets(str)) {
        if (Find_str(str))
            puts("YES");
        else
            puts("NO");
    }
    return 0;
}

只要了解了插入操作,查询操作就很容易理解了,字典树基本上就是这样子的了,但是在很多题中不只是简简单单的查单词,要根据题中的实际情况来做一些修改,但是一般都是大同小异,后面的例题可以看下,就要根据不同的情况来修改字典树。

01字典树

01字典树主要用于解决求异或最值的问题,跟字典树的功能有一些不一样(要是对于基本的异或操作还不是很熟悉的可以先去了解一下异或操作)。其实01字典树的插入和查找操作跟字典树基本上差不多,只是字典树插入的是一个字符串,而01字典树插入的是一个二进制的数字串。

现在来说说为什么01字典树可以解决异或最值问题。

假如现在给出一个数,现在我们要找一个数与该数异或值最大,我们应该怎么找,这里我们要用的(0^1=1,0^0=0,1^1=0)基本异或操作,现在我们只需要把这个数化成二进制数,然后从高位依次向下找(为什么从高位开始找,后面再解释),如果这个数该位是0,我们就要找该位是1的数,要是该位是1的话,我们就要找该位是0的数。例如:我们要找二进制为1001的数的异或最大值,另一个数肯定是0110,这样异或下来为1111,结果最大。(只要这里能懂,基本上01字典树就学习了一半了)。

01字典树的插入和查找操作(因为跟字典树差不多,我就直接贴出来了)。

void Insert(ll n) {///插入数字
    int root = 0;///还是要从根节点开始插入
    for (int i = 32; i >= 0; i--) {
        int id = (n >> i) & 1;///现在我们从最高位开始插入,获取该位是0还是1
        if (!tree[root][id])///这里和字典树原理一下,就不多解释了
            tree[root][id] = ++tot;
        root = tree[root][id];
    }
    val[root] = n;///因为我们后面要找与之异或数最大的数所以用一个val数组把对应节点的值保存下来。
}

ll get_ans(ll n) {
    int root = 0;;///还是要从根节点开始查找
    for (int i = 32; i >= 0; i--) {
        int id = (n >> i) & 1;///现在我们从最高位开始查找,获取该位是0还是1
        if (tree[root][id ^ 1])///要是有与该位数字不一样的(肯定要找不一样的,这样异或出来该位才是1)
            root = tree[root][id ^ 1];
        else
            root = tree[root][id];///如果没有,只能按照原来的数像下找
    }
    return val[root];///返回对应的节点值
}

现在我来解释一下,其中一些代码为什么这样写。

1、01字典树就是一棵最多 32层(如果插入的数在32位之内)的二叉树,其每个节点的两条边分别表示二进制的某一位的值为 0 还是为 1. 将某个路径上边的值连起来就得到一个二进制串。

2、在插入操作和查找操作都是插入32位是为什么:因为题中给出的数字可能只是在int范围内,每个数字的二进制位数不一样,我们为了操作方便每一个数字都插入32位,要是不足32位的数,我们前面用0补齐,最后不影响答案。这样插入的好处,我们在查询的时候就不用考虑两个数组位数不同的数了。例如:插入数字二进制为:11110001,查找数字二进制为:1110。这样其实查找过程中我们是查找的是0011110001,0000001110(这里补齐成10位),这样查到起来就很明了了。

3、为什么要从最高位开始查找:这里其实是用到了贪心策略,因为我们要找异或最大的,肯定越高位不同,异或出来的数字越大。例如:11000,跟00111和01111异或,跟前者异或是11000,跟后者异或是10111。

4、为什么没有判断找不到的情况:因为我们插入和查到的都是一个二进制串,在查找过程中要么走0这条路,要么走0这条路,只要是01字典树里面插入的有值就一定能找到。

5.这里为什么要这样写:

        if (tree[root][id ^ 1])///要是有与该位数字不一样的(肯定要找不一样的,这样异或出来该位才是1)
            root = tree[root][id ^ 1];
        else
            root = tree[root][id];///如果没有,只能按照原来的数像下找

因为要找异或值最大,肯定优先找与之位不同的数。如果该位是0,我们要优先走1(0^1=1)这条路,要是该位是1,我们优先走0(1^1=0)这条路。

01字典树基本上就是这么多了,可以看看后面的例题。

可持久化01字典树要在学了主席树的前提下,后期再更新。

字典树例题:一套字典树的题

power oj 2390: 查单词

题解:这是一个字典树的模板题,将上述字典树模板的vis数组编辑换成了一个sum记录个数的数组(这个题动态字典树过不了,这道题跟 HDU 1251 统计难题差不多)。

#include <iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<algorithm>
#include<map>
#include<queue>
#include<set>
#include<cmath>
#include<stack>
#include<string>

const int maxn = 5e5 + 5;
const int mod = 10007;
const int inf = 1e9;
const long long onf = 1e18;
#define me(a, b) memset(a,b,sizeof(a))
#define lowbit(x) x&(-x)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define PI 3.14159265358979323846
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
int tree[maxn][30], tot;
int sum[maxn];

void Insert(char *str) {
    int len = strlen(str);
    int root = 0;
    for (int i = 0; i < len; i++) {
        int id = str[i] - 'a';
        if (!tree[root][id])
            tree[root][id] = ++tot;
        root = tree[root][id];
        sum[root]++;
    }
}

int Find_str(char *str) {
    int root = 0;
    int len = strlen(str);
    for (int i = 0; i < len; i++) {
        int id = str[i] - 'a';
        if (!tree[root][id])
            return 0;
        root = tree[root][id];
    }
    return sum[root];
}

int main() {
    char str[30];
    while (gets(str) && strlen(str)) {
        Insert(str);
    }
    while (gets(str))
        printf("%d\n", Find_str(str));
    return 0;
}

 

HDU 1671 Phone List

题意:判断一些单词是里,有没有单词是其他单词的前缀,有的话输出NO,没有输出YES。

题解:这个题都不用查找函数,直接在插入的时候判断就行了。

#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
#include<queue>
#include<set>
#include<cmath>
#include<stack>
#include<string>

const int maxn = 1e5 + 5;
const int mod = 10007;
const int inf = 1e9;
const long long onf = 1e18;
#define me(a, b) memset(a,b,sizeof(a))
#define lowbit(x) x&(-x)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define PI 3.14159265358979323846
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
int tree[maxn][15], cnt;
bool vis[maxn];
bool flag;

void Insert(char *str) {
    int root = 0;
    int len = strlen(str);
    for (int i = 0; i < len; i++) {
        int id = str[i] - '0';
        if (!tree[root][id]) {
            tree[root][id] = ++cnt;
        } else if (i == len - 1) {
            flag = true;
        }
        if (vis[root])
            flag = 1;
        root = tree[root][id];
    }
    vis[root] = true;
}

int main() {
    int t;
    scanf("%d", &t);
    while (t--) {
        int n;
        flag = 0, cnt = 1;
        me(tree, 0), me(vis, 0);
        scanf("%d", &n);
        char str[20];
        for (int i = 0; i < n; i++) {
            scanf("%s", str);
            if (flag)
                continue;
            Insert(str);
        }
        if (flag)
            puts("NO");
        else
            puts("YES");
    }
    return 0;
}

 

HDU 1004Let the Balloon Rise

题意:找出出现次数最多的字符串(不用字典树来做,但是可以拿来练手)。

#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
#include<queue>
#include<set>
#include<cmath>
#include<stack>
#include<string>
const int maxn=1e5+5;
const int mod=10007;
const int inf=1e9;
const long long onf=1e18;
#define me(a,b) memset(a,b,sizeof(a))
#define lowbit(x) x&(-x)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define PI 3.14159265358979323846
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
int tmax;
char ans[20];
int Max[1005];
int tree[maxn][30],cnt=0;
bool vis[maxn];
map<string,int>q;
void Insrt(char *str){
    int len=strlen(str);
    int root=0;
    for(int i=0;i<len;i++){
        int id=str[i]-'a';
        if(!tree[root][id])
            tree[root][id]=++cnt;
        root=tree[root][id];
        if(!Max[q[str]]||(i==len-1&&vis[root])){
            Max[q[str]]++;
            if(Max[q[str]]>tmax){
                strcpy(ans,str);
                tmax=Max[q[str]];
            }
        }
    }
    vis[root]=1;
}
int main()
{
    int n;
    while(scanf("%d",&n)&&n){
        char str[20];
        me(Max,0),q.clear();
        int len=0;
        tmax=-1;
        for(int i=0;i<n;i++){
            scanf("%s",str);
            if(!q[str]){
                q[str]=len++;
            }
            Insrt(str);
        }
        printf("%s\n",ans);
    }
    return 0;
}

 

HDU 1075 What Are You Talking About

题意:现在给你一个字典,里面有火星文转成成对应地球文,然后给你一个火星文字符串让你转换成地球文。

题解:将上述模板加一个保存字符串的数组即可。

#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
#include<queue>
#include<set>
#include<cmath>
#include<stack>
#include<string>

const int maxn = 1e6 + 5;
const int mod = 10007;
const int inf = 1e9;
const long long onf = 1e18;
#define me(a, b) memset(a,b,sizeof(a))
#define lowbit(x) x&(-x)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define PI 3.14159265358979323846
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
char ans[20];
char record[maxn][15];
bool vis[maxn];
int tree[maxn][30];
int cnt;

void Insert(char *str, char *s) {
    int root = 0;
    int len = strlen(str);
    for (int i = 0; i < len; i++) {
        int id = str[i] - 'a';
        if (!tree[root][id])
            tree[root][id] = ++cnt;
        root = tree[root][id];
    }
    strcpy(record[root], s);
    vis[root] = 1;
}

bool Find_str(char *str) {
    int root = 0;
    int len = strlen(str);
    for (int i = 0; i < len; i++) {
        int id = str[i] - 'a';
        if (!tree[root][id])
            return false;
        root = tree[root][id];
    }
    if (vis[root]) {
        strcpy(ans, record[root]);
        return true;
    }
    return false;
}

int main() {
    char str[30000];
    scanf("%s", str);
    while (scanf("%s", str) && strcmp(str, "END") != 0) {
        char str1[20];
        scanf("%s", str1);
        Insert(str1, str);
    }
    scanf("%s", str);
    getchar();
    while (gets(str) && strcmp(str, "END") != 0) {
        int len = strlen(str);
        char temp[20];
        int l = 0;
        for (int i = 0; i < len; i++) {
            if (str[i] >= 'a' && str[i] <= 'z') {
                temp[l++] = str[i];
            } else {
                temp[l] = '\0';
                if (Find_str(temp))
                    printf("%s", ans);
                else
                    printf("%s", temp);
                printf("%c", str[i]);
                l = 0;
            }
        }
        printf("\n");
    }
    return 0;
}

 

HDU 4287 Intelligent IME

题意:给出的按键方法(数字)可以匹配多少个字符串。

题解:先将26个字母离散成对应的数字。输入字符串时,通过上面的离散数组,将其还原为数字表示,然后用O(1)的方法更新相应按键方法的匹配次数即可。

#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
#include<queue>
#include<set>
#include<cmath>
#include<stack>
#include<string>

const int maxn = 5e4 + 5;
const int mod = 10007;
const int inf = 1e9;
const long long onf = 1e18;
#define me(a, b) memset(a,b,sizeof(a))
#define lowbit(x) x&(-x)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define PI 3.14159265358979323846
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
int tree[maxn][15], tot, sum[maxn];
char cnt[300];
char a[maxn][10];

void Insert(char *str) {
    int root = 0;
    int len = strlen(str);
    for (int i = 0; i < len; i++) {
        int id = cnt[str[i]] - '0';
        if (!tree[root][id])
            tree[root][id] = ++tot;
        root = tree[root][id];
    }
    sum[root]++;
}

int query(char *str) {
    int root = 0;
    int len = strlen(str);
    for (int i = 0; i < len; i++) {
        int id = str[i] - '0';
        if (!tree[root][id])
            return 0;
        root = tree[root][id];
    }
    return sum[root];
}

int main() {
    int t;
    scanf("%d", &t);
    cnt['a'] = '2', cnt['b'] = '2', cnt['c'] = '2', cnt['d'] = '3', cnt['e'] = '3', cnt['f'] = '3', cnt['g'] = '4';
    cnt['h'] = '4', cnt['i'] = '4', cnt['j'] = '5', cnt['k'] = '5', cnt['l'] = '5', cnt['m'] = '6', cnt['n'] = '6';
    cnt['o'] = '6', cnt['p'] = '7', cnt['q'] = '7', cnt['r'] = '7', cnt['s'] = '7', cnt['t'] = '8', cnt['u'] = '8';
    cnt['v'] = '8', cnt['w'] = '9', cnt['x'] = '9', cnt['y'] = '9', cnt['z'] = '9';
    while (t--) {
        int n, m;
        scanf("%d%d", &n, &m);
        for (int i = 0; i < n; i++)
            scanf("%s", a[i]);
        me(tree, 0), me(sum, 0), tot = 0;
        for (int i = 0; i < m; i++) {
            char str[20];
            scanf("%s", str);
            Insert(str);
        }
        for (int i = 0; i < n; i++) {
            printf("%d\n", query(a[i]));
        }
    }
    return 0;
}

 

HDU 1247 Hat’s Words

题意:给出一些字符串,让你求出是否有字符串是其他两个字符串相加得到。

题解:先把所以字符串插入,然后在每个字符串特判就行了。

#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
#include<queue>
#include<set>
#include<cmath>
#include<stack>
#include<string>

const int maxn = 5e5 + 5;
const int mod = 10007;
const int inf = 1e9;
const long long onf = 1e18;
#define me(a, b) memset(a,b,sizeof(a))
#define lowbit(x) x&(-x)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define PI 3.14159265358979323846
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
int tree[maxn][30], tot;
bool vis[maxn];
char str[maxn][30];

void Insert(char *str) {
    int len = strlen(str);
    int root = 0;
    for (int i = 0; i < len; i++) {
        int id = str[i] - 'a';
        if (!tree[root][id])
            tree[root][id] = ++tot;
        root = tree[root][id];
    }
    vis[root] = 1;
}

bool Find_str(char *str) {
    int root = 0;
    int len = strlen(str);
    for (int i = 0; i < len; i++) {
        int id = str[i] - 'a';
        if (!tree[root][id])
            return false;
        root = tree[root][id];
    }
    return vis[root];
}

int main() {
    int len = 0;
    while (scanf("%s", str[len]) != EOF) {
        Insert(str[len++]);
    }
    for (int i = 0; i < len; i++) {
        int l = strlen(str[i]);
        for (int j = 0; j < l; j++) {
            char temp1[30], temp2[30];
            strcpy(temp1, str[i]);
            temp1[j] = '\0';
            strcpy(temp2, str[i] + j);
            if (Find_str(temp1) && Find_str(temp2)) {
                puts(str[i]);
                break;
            }
        }
    }
    return 0;
}

 

01字典树例题

HDU 4825 Xor Sum

题解:01字典树模板题。

#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
#include<queue>
#include<set>
#include<cmath>
#include<stack>
#include<string>

const int maxn = 1e5 + 5;
const int mod = 10007;
const int inf = 1e9;
const long long onf = 1e18;
#define me(a, b) memset(a,b,sizeof(a))
#define lowbit(x) x&(-x)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define PI 3.14159265358979323846
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
int tree[32 * maxn][2], val[32 * maxn], tot;

void Insert(ll n) {
    int root = 0;
    for (int i = 32; i >= 0; i--) {
        int id = (n >> i) & 1;
        if (!tree[root][id])
            tree[root][id] = ++tot;
        root = tree[root][id];
    }
    val[root] = n;
}

ll get_ans(ll n) {
    int root = 0;
    for (int i = 32; i >= 0; i--) {
        int id = (n >> i) & 1;
        if (tree[root][id ^ 1])
            root = tree[root][id ^ 1];
        else
            root = tree[root][id];
    }
    return val[root];
}

int main() {
    int t, Case = 1;
    scanf("%d", &t);
    while (t--) {
        int n, m;
        ll x;
        tot = 0, me(tree, 0);
        scanf("%d%d", &n, &m);
        for (int i = 0; i < n; i++) {
            scanf("%lld", &x);
            Insert(x);
        }
        printf("Case #%d:\n", Case++);
        for (int i = 0; i < m; i++) {
            scanf("%lld", &x);
            printf("%lld\n", get_ans(x));
        }
    }
    return 0;
}

 

HDU 5536 Chip Factory

题解:这个题暴力可以过,但是可以用01字典树,因为i,j,k。不能相等,所以我们枚举a[i],a[j],每次把这两个数从字典树中拿出来,然后在进去找与a[i]+a[j]异或最大的,复杂度O(n^2*32)。

#include <iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<algorithm>
#include<map>
#include<queue>
#include<set>
#include<cmath>
#include<stack>
#include<string>

const int maxn = 1e3 + 5;
const int mod = 10007;
const int inf = 1e9;
const long long onf = 1e18;
#define me(a, b) memset(a,b,sizeof(a))
#define lowbit(x) x&(-x)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define PI 3.14159265358979323846
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
int tree[32 * maxn][2], tot;
int val[32 * maxn], sum[maxn * 32], a[maxn];

void Insert(int n, int cnt) {
    int root = 0;
    for (int i = 31; i >= 0; i--) {
        int id = (n >> i) & 1;
        if (!tree[root][id])
            tree[root][id] = ++tot;
        root = tree[root][id];
        sum[root] += cnt;
    }
    val[root] = n;
}

int Find_n(int n) {
    int root = 0;
    for (int i = 31; i >= 0; i--) {
        int id = (n >> i) & 1;
        if (tree[root][id ^ 1] && sum[tree[root][id ^ 1]] > 0)
            root = tree[root][id ^ 1];
        else
            root = tree[root][id];
    }
    return val[root] ^ n;
}

int main() {
    int t;
    scanf("%d", &t);
    while (t--) {
        int n;
        scanf("%d", &n);
        me(tree, 0), me(sum, 0);
        tot = 0;
        for (int i = 1; i <= n; i++) {
            scanf("%d", &a[i]);
            Insert(a[i], 1);
        }
        int ans = 0;
        for (int i = 1; i <= n; i++) {
            Insert(a[i], -1);
            for (int j = i + 1; j <= n; j++) {
                Insert(a[j], -1);
                ans = max(ans, Find_n(a[i] + a[j]));
                Insert(a[j], 1);
            }
            Insert(a[i], 1);
        }
        printf("%d\n", ans);
    }
    return 0;
}

 

HDU 5269 ZYB loves Xor I

题意:给出一个长度为n的数组A,让你求出lowbit(A[i]^A[j])的和对998244353取模的结果,其中i和j都属于[1,n]。lowbit(x)表示的是满足x xor 2^k > 0最小的2^k

题解:对于异或,如果x xor 2^k > 0的话,k一定是x二进制表示中最小的一位为1的数。所以我们很容易想到01字典树,01字典树第h层的左子树表示数据自右向左第h位为0的,右子树表示第h位为1。而这两个如果同时存在,那么这两个节点当前能表示的数的个数的乘积乘以1 << h就是这两个节点能表示的数的lowbit值。

#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
#include<queue>
#include<set>
#include<cmath>
#include<stack>
#include<string>

const int maxn = 5e4 + 5;
const int mod = 998244353;
const int inf = 1e9;
const long long onf = 1e18;
#define me(a, b) memset(a,b,sizeof(a))
#define lowbit(x) x&(-x)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define PI 3.14159265358979323846
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
ll sum[32 * maxn], tree[maxn * 32][2], tot, er[30];
ll ans;

void init() {
    er[0] = 1;
    for (int i = 1; i <= 30; i++)
        er[i] = er[i - 1] * 2;
}

void Insert(ll n) {
    int root = 0;
    for (int i = 0; i <= 30; i++) {
        int id = (n >> i) & 1;
        if (!tree[root][id]) {
            tree[root][id] = ++tot;
            tree[tot][0] = tree[tot][1] = 0;
            sum[tot] = 0;
        }
        root = tree[root][id];
        sum[root]++;
    }
}

void dfs(int root, int step) {
    int l = tree[root][0], r = tree[root][1];
    if (l && r) {
        ans = (ans + sum[l] * sum[r] * er[step] % mod) % mod;
        dfs(l, step + 1), dfs(r, step + 1);
    } else if (l)
        dfs(l, step + 1);
    else if (r)
        dfs(r, step + 1);
}

int main() {
    int t, Case = 1;
    scanf("%d", &t);
    init();
    while (t--) {
        int n;
        tot = 0;
        tree[0][1] = tree[0][0] = 0, sum[0] = 0;
        scanf("%d", &n);
        for (int i = 0; i < n; i++) {
            ll x;
            scanf("%lld", &x);
            Insert(x);
        }
        ans = 0;
        dfs(0, 1);
        printf("Case #%d: %d\n", Case++, ans);
    }
    return 0;
}

 

CodeForces - 842D Vitya and Strange Lesson

题意:求数组的mex。

题解:因为所求mex是未出现的最小整数,所以我们可以提前把所以未出现的数插入进去,然后直接求异或过后,最小的数。

#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
#include<queue>
#include<set>
#include<cmath>
#include<stack>
#include<string>

const int maxn = 3e5 + 5;
const int mod = 10007;
const int inf = 1e9;
const long long onf = 1e18;
#define me(a, b) memset(a,b,sizeof(a))
#define lowbit(x) x&(-x)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define PI 3.14159265358979323846
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
int tree[31 * maxn][2], val[maxn * 31], tot;
bool vis[maxn * 2];

void Insert(int n) {
    int root = 0;
    for (int i = 31; i >= 0; i--) {
        int id = (n >> i) & 1;
        if (!tree[root][id])
            tree[root][id] = ++tot;
        root = tree[root][id];
    }
    val[root] = n;
}

int get_ans(int n) {
    int root = 0;
    for (int i = 31; i >= 0; i--) {
        int id = (n >> i) & 1;
        if (tree[root][id])
            root = tree[root][id];
        else
            root = tree[root][id ^ 1];
    }
    return n ^ val[root];
}

int main() {
    int n, m, x;
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; i++) {
        scanf("%d", &x);
        vis[x] = true;
    }
    for (int i = 0; i < maxn * 2; i++) {
        if (!vis[i])
            Insert(i);
    }
    int temp = 0;
    for (int i = 0; i < m; i++) {
        scanf("%d", &x);
        temp ^= x;
        printf("%d\n", get_ans(temp));
    }
    return 0;
}

 

POJ 3764 The xor-longest Path 

题意:现在给出一棵树,每条边有自己的权值,现在让你找一条路,路的长度定义为所经过边的权值全部异或起来的值,要求路的长度最长。

题解:现在我们用一个数组记录0节点到该节点的所有路径权值异或后的结果(这里将0节点看成根节点),然后将数组里面的值全部插入到一个01字典树中(基本操作),然后就是这个题最巧妙的地方了,然后拿数组去这课树上找异或值最大的值就是答案,解释一下:要是这条路线经过0节点,直接异或就是答案,要是两节点在同一棵子树,说明有些两个节点到0节点有些路径走了两遍,但是两个相等的数异或等于0,所以就相当于没有走那条路,所以答案还是两节点的中间的路径权值的异或值。其他就是基本操作了。

#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
#include<queue>
#include<set>
#include<cmath>
#include<stack>
#include<string>

const int maxn = 1e5 + 5;
const int mod = 10007;
const int inf = 1e9;
const long long onf = 1e18;
#define me(a, b) memset(a,b,sizeof(a))
#define lowbit(x) x&(-x)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define PI 3.14159265358979323846
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
int cnt, tot;
int n;
int head[maxn << 1];
int tree[maxn * 32][3];
int val[maxn * 32];
int cnt_xor[maxn];
struct node {
    int v, w, next;
} maps[maxn << 1];

void add_edge(int u, int v, int w) {
    maps[cnt].w = w;
    maps[cnt].v = v;
    maps[cnt].next = head[u];
    head[u] = cnt++;
}

void dfs(int u, int fa) {
    for (int i = head[u]; ~i; i = maps[i].next) {
        int w = maps[i].w;
        int v = maps[i].v;
        if (v == fa)
            continue;
        cnt_xor[v] = cnt_xor[u] ^ w;///保存0节点到该节点路径权值异或值
        dfs(v, u);
    }
}

int Insert(int n) {
    int root = 0;
    for (int i = 31; i >= 0; i--) {
        int id = (n >> i) & 1;
        if (!tree[root][id])
            tree[root][id] = ++tot;
        root = tree[root][id];
    }
    val[root] = n;
}

int query(int n) {
    int root = 0;
    for (int i = 31; i >= 0; i--) {
        int id = (n >> i) & 1;
        if (tree[root][id ^ 1]) {
            root = tree[root][id ^ 1];
        } else
            root = tree[root][id];
    }
    return n ^ val[root];
}

void init() {
    me(head, -1);
    me(tree, 0);
    tot = cnt = 0;
}

int main() {
    while (scanf("%d", &n) != EOF) {
        init();
        for (int i = 1; i < n; i++) {
            int u, v, w;
            scanf("%d%d%d", &u, &v, &w);
            add_edge(u, v, w);
            add_edge(v, u, w);
        }
        dfs(0, -1);
        for (int i = 0; i < n; i++)
            Insert(cnt_xor[i]);
        int ans = 0;
        for (int i = 0; i < n; i++)
            ans = max(ans, query(cnt_xor[i]));
        printf("%d\n", ans);
    }
    return 0;
}

 

可持续化01字典树例题:

HDU 4757 Tree

题意:现在给出一棵树,每个节点有相应的权值,现在给出树上任意两个节点和一个值,要求求出这两个节点路径中路过的节点与该值异或的最大值。

题解:既然要求求两节点路径,就说明肯定要先算两节点的LCA,我们从根节点开始建可持续化字典树,然后子节点在父节点的基础上再建,这样建树后,例如我们要找x,y两节点中间的异或最大值,就是求max(root[x]-root[father[LCA]],root[y]-root[father[LCA]])。

#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
#include<queue>
#include<set>
#include<cmath>
#include<stack>
#include<string>

const int maxn = 1e5 + 5;
const int mod = 10007;
const int inf = 1e9;
const long long onf = 1e18;
#define me(a, b) memset(a,b,sizeof(a))
#define lowbit(x) x&(-x)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define PI 3.14159265358979323846
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
int tot, cnt;
int n, m;
int head[maxn << 1];
int val[maxn];
int father[maxn][24], depth[maxn];
int tree[maxn * 20][2], son[maxn * 20][2];
int root[maxn];
struct node {
    int v, next;
} maps[maxn << 1];

void add_edge(int u, int v) {
    maps[cnt].v = v;
    maps[cnt].next = head[u];
    head[u] = cnt++;
}

void build(int pre, int &cur, int n, int pos) {
    if (pos < 0)
        return;
    cur = ++tot;
    int id = (n & (1 << pos)) > 0;
    tree[cur][id] = tree[pre][id] + 1;
    tree[cur][id ^ 1] = tree[pre][id ^ 1];
    son[cur][id ^ 1] = son[pre][id ^ 1];
    build(son[pre][id], son[cur][id], n, pos - 1);
}

int query(int pre, int cur, int n, int sum, int pos) {
    if (pos < 0)
        return sum;
    int id = (n & (1 << pos)) > 0;
    int s = tree[cur][id ^ 1] - tree[pre][id ^ 1];
    if (s > 0)
        return query(son[pre][id ^ 1], son[cur][id ^ 1], n, sum + (1 << pos), pos - 1);
    return query(son[pre][id], son[cur][id], n, sum, pos - 1);

}

void dfs(int u, int fa) {
    build(root[fa], root[u], val[u], 16);///在父节点的基础上建,子节点建可持续化字典树
    father[u][0] = fa;
    depth[u] = depth[fa] + 1;
    for (int i = 1; (1 << i) <= n; i++)
        father[u][i] = father[father[u][i - 1]][i - 1];
    for (int i = head[u]; ~i; i = maps[i].next) {
        int v = maps[i].v;
        if (v == fa)
            continue;
        dfs(v, u);
    }
}

int get_LCA(int u, int v) {///求LCA
    if (depth[u] > depth[v])
        swap(u, v);
    for (int ret = depth[v] - depth[u], i = 0; ret; ret >>= 1, i++) {
        if (ret & 1) {
            v = father[v][i];
        }
    }
    if (u == v)
        return u;
    for (int i = log2(n); i >= 0; i--) {
        if (father[u][i] == father[v][i])
            continue;
        u = father[u][i];
        v = father[v][i];
    }
    return father[u][0];
}

void init() {
    me(head, -1);
    me(tree, 0), me(son, 0);
    tot = cnt = 0;
}

int main() {
    while (scanf("%d%d", &n, &m) != EOF) {
        init();
        for (int i = 1; i <= n; i++)
            scanf("%d", &val[i]);
        for (int i = 1; i < n; i++) {
            int u, v;
            scanf("%d%d", &u, &v);
            add_edge(u, v);
            add_edge(v, u);
        }
        dfs(1, 0);
        while (m--) {
            int l, r, x;
            scanf("%d%d%d", &l, &r, &x);
            int temp = get_LCA(l, r);
            int ans = max(query(root[father[temp][0]], root[l], x, 0, 16),query(root[father[temp][0]], root[r], x, 0, 16));
            printf("%d\n", ans);
        }
    }
    return 0;
}

 

HDU-6191 Query on A Tree

题意:现在给出一棵树,树的每个节点都有相应的权值,然后每次询问给出一个节点和一个值,要求求出以该节点为根的子树中的节点与该值异或后的最大值。

题解:首先我们肯定不能在树上直接操作,我们先用DFS序将树化成一个线性结构,然后对于子树的查找就是对对应一个子区间查找异或最大值,这个就可以用可持续化字典树来维护。
 

#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
#include<queue>
#include<set>
#include<cmath>
#include<stack>
#include<string>

const int maxn = 1e5 + 5;
const int mod = 10007;
const int inf = 1e9;
const long long onf = 1e18;
#define me(a, b) memset(a,b,sizeof(a))
#define lowbit(x) x&(-x)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define PI 3.14159265358979323846
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
int cnt, tot, tot1;
int n, q;
int head[maxn << 1];
int val[maxn * 32];
int in[maxn], out[maxn];
int root[maxn];
struct node {
    int v, next;
} maps[maxn << 1];
int tree[maxn * 32][2], son[maxn * 32][2];

void add_edge(int u, int v) {
    maps[cnt].v = v;
    maps[cnt].next = head[u];
    head[u] = cnt++;
}

void build(int pre, int &cur, int n, int pos) {
    if (pos < 0)
        return;
    cur = ++tot;
    int id = (n & (1 << pos)) > 0;
    tree[cur][id] = tree[pre][id] + 1;
    tree[cur][id ^ 1] = tree[pre][id ^ 1];
    son[cur][id ^ 1] = son[pre][id ^ 1];
    build(son[pre][id], son[cur][id], n, pos - 1);
}

int query(int pre, int cur, int n, int sum, int pos) {
    if (pos < 0)
        return sum;
    int id = (n & (1 << pos)) > 0;
    int s = tree[cur][id ^ 1] - tree[pre][id ^ 1];
    if (s > 0)
        return query(son[pre][id ^ 1], son[cur][id ^ 1], n, sum + (1 << pos), pos - 1);
    return query(son[pre][id], son[cur][id], n, sum, pos - 1);

}

void DFS(int u) {
    in[u] = ++tot1;
    build(root[in[u] - 1], root[in[u]], val[u], 30);
    for (int i = head[u]; ~i; i = maps[i].next) {
        int v = maps[i].v;
        DFS(v);
    }
    out[u] = tot1;
}

void init() {
    me(head, -1);
    me(tree, 0), me(son, 0);
    tot1 = tot = cnt = 0;
}

int main() {
    while (scanf("%d%d", &n, &q) != EOF) {
        init();
        for (int i = 1; i <= n; i++)
            scanf("%d", &val[i]);
        for (int i = 2; i <= n; i++) {
            int x;
            scanf("%d", &x);
            add_edge(x, i);
        }
        DFS(1);
        while (q--) {
            int u, x;
            scanf("%d%d", &u, &x);
            printf("%d\n", query(root[in[u] - 1], root[out[u]], x, 0, 30));
        }
    }
    return 0;
}

以上就是我的一些了解,若有更好的解法,或者我的解法有问题,请大神评论在下方,谢谢。

©️2020 CSDN 皮肤主题: 创作都市 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值