Tire树的基本应用

上一篇主要讲了Tire的基本操作与实现方法,接下来开始了解字典树的具体应用。

1.phone list(判断一个字符串是不是另一个字符串的前缀)

原题链接:http://poj.org/problem?id=3630

Description

Given a list of phone numbers, determine if it is consistent in the sense that no number is the prefix of another. Let's say the phone catalogue listed these numbers:

  • Emergency 911
  • Alice 97 625 999
  • Bob 91 12 54 26

In this case, it's not possible to call Bob, because the central would direct your call to the emergency line as soon as you had dialled the first three digits of Bob's phone number. So this list would not be consistent.

Input

The first line of input gives a single integer, 1 ≤ t ≤ 40, the number of test cases. Each test case starts with n, the number of phone numbers, on a separate line, 1 ≤ n ≤ 10000. Then follows n lines with one unique phone number on each line. A phone number is a sequence of at most ten digits.

Output

For each test case, output "YES" if the list is consistent, or "NO" otherwise.

Sample Input

2
3
911
97625999
91125426
5
113
12340
123440
12345
98346

Sample Output

NO
YES

描述:第一行,一个整数t,表示t组数据,对于每组数据,第一行一个n,接下来n行,输入n个字符串。若存在两个字符串s,t,使s是t的前缀,则输出NO,否则输出YES。

思路:首先将所有字符串都插入到Tire树中,若一个字符串是另一个字符串的前缀则有以下两种情况:

1.该字符串插入Tire树时,并未创建任何新结点。(该串是之前插入的某个串前缀。)

2.该字符串在插入Tire树时,经过某一个结点时,该结点有串结束标志。(之前插入的某个串为当前串前缀。)

#include<cstdio>
#include<cstring>
const int N=1e5+5;
const int Z=10;
int ch[N][Z],tot;
bool bo[N];
char s[20];
bool insert_ch(char* s){
    int len=strlen(s);
    int u=1;
    bool flag=false;
    for(int i=0;i<len;++i){
        int c=s[i]-'0';
        if(!ch[u][c])ch[u][c]=++tot;
        else if(i==len-1)flag=true;
        u=ch[u][c];
        if(bo[u])flag=true;
    }
    bo[u]=true;
    return flag;
}
int main(){
    int t,n;
    scanf("%d",&t);
    while(t--){
        bool ans=false;
        memset(ch,0,sizeof(ch));
        memset(bo,false,sizeof(bo));
        tot=1;
        scanf("%d",&n);
        while(n--){
            scanf("%s",s);
            if(insert_ch(s))ans=true;
        }
        if(ans)printf("NO\n");
        else printf("YES\n");
    }
    return 0;
}
        
        

2.秋实大哥与快餐店(数与数的异或运算)

原题链接:http://acm.zcmu.edu.cn/JudgeOnline/problem.php?id=1783

Description

朝为田舍郎,暮登天子堂。秋实大哥从小就怀抱有远大的理想,所以他开了一家快餐店。

秋实大哥根据菜的口感,给每一道菜一个唯一的CID,同时对于前来的客人,根据他们的口味喜好,秋实大哥会给每一个客人一个PID。

对于一个标号为PID的客人,他对标号为CID的菜的喜爱程度为PID∧CID(∧表示按位异或),该值越大表示越喜欢。

秋实大哥实在太忙了,现在他需要你来帮忙照看一下他的店铺。

 

Input

第一行包含一个整数n,表示秋实大哥的餐馆内现在有n道菜。接下来一行包含n个整数,分别表示每一道菜的CID。

接下来一行包含一个整数m,表示接下来发生了m件事。接下来的m行,每一行为以下两种事件之一:

0 c : 表示秋实大哥最新研制出一道标号为c的菜

1 p : 表示来了一位标号为p的客人,请你在已有的菜中找出一道他最喜爱的菜

1≤n,m≤100000,0≤PID,CID≤1000000。

 

Output

对于每一个1 p事件输出一个整数,表示该客人最喜欢的菜的标号

 

Sample Input

1 1 3 1 1 0 2 1 1

Sample Output

1 2

思路,给一组数,再给一个数n,求在那一组数中与n异或值最大的数是多少?解决如下问题:

1、如何使异或值最大???把每个整数转变为32位(根据题目给出的数据范围,不足高位补0)的01字符串。将字符串插入Tire树中。当查询与n异或最大的值时,把n也转变成32位字符串接着在Tire中进行类似查询的操作,每一步都尝试着沿着字符串当前位相反的字符指针向下访问,如果与当前位相反的字符指针为空,则只能沿着当前位向下访问,根据异或的性质,相同为0,相异为1。如此访问下去就是与n异或结果最大的数。

2、如何找与n异或最大的那个数???再开一个数组,保存该结点的数值(注意下标)。

3、与此题类似的还有,给n个数,选出两个进行异或操作,求异或的结果的最大值。此时还是将n个数先变成01串插入Tire数中,然后进行访问其相反路径操作。代码请自行写出。

#include<bits/stdc++.h>
using namespace::std;
const int maxn=1<<22;
int tire[maxn+4][2],val[maxn+4];
int tot;
void insert_tire(int x){
    int u=1;
    for(int i=21;i>=0;--i){
        int c=(x>>i)&1;
        if(!tire[u][c])tire[u][c]=++tot;
        u=tire[u][c];
    }
    val[u<<1]=x;//最后一个结点(叶子结点)的左孩子结点存其数值。
}
int query(int x){
    int u=1;
    for(int i=21;i>=0;--i){
        int c=(x>>i)&1;
        if(tire[u][!c]){
            u=tire[u][!c];
        }else{
            u=tire[u][c];
        }
    }
    return val[u<<1];
}
int main(){
    int n,m,x;
    while(~scanf("%d",&n)){
        memset(tire,0,sizeof(tire));
        memset(val,0,sizeof(val));
        tot=1;
        for(int i=0;i<n;++i){
            scanf("%d",&x);
            insert_tire(x);
        }
        scanf("%d",&m);
        while(m--){
            int x,y;
            scanf("%d%d",&x,&y);
            if(!x){
                insert_tire(y);
            }else{
                printf("%d\n",query(y));
            }
        }
    }

    return 0;
}

3.codechef REBXOR(Tire树与前缀和运用)

原题链接:https://www.lydsy.com/JudgeOnline/problem.php?id=4260

Description

Input

输入数据的第一行包含一个整数N,表示数组中的元素个数。

第二行包含N个整数A1,A2,…,AN。

 

Output

输出一行包含给定表达式可能的最大值。

Sample Input

5
1 2 3 1 2

Sample Output

6

HINT

满足条件的(l1,r1,l2,r2)有:(1,2,3,3),(1,2,4,5),(3,3,4,5)。

 

对于100%的数据,2 ≤ N ≤ 4*105,0 ≤ Ai ≤ 109。

思路:前缀和与异或的综合运用。令L【i】表示1<=l<=r<=i的最大a[1]^a[1+1]^.....a[r],r【i】表示i<=l<=r<=n的最大a[L]^a[L+1]......^a[r],则结果最大的L【i】+r【i+1】。问题转化为求L【i】和r【i】。设x【i】表示a[0]^a[1].....a[i],那么L【i】=max(x[i]^x[j])(0<=j<i)。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace::std;
const int N=4e5+5;
const int Z=2;
int n,tot=1;
int ch[N<<5][Z],l[N],r[N],a[N];
void  insert(int x){
    int u=1;
    for(int i=1<<30;i;i>>=1){
        int c=(x&i)?1:0;
        if(!ch[u][c])ch[u][c]=++tot;
        u=ch[u][c];
    }
}
int find(int x){
    int u=1,ans=0;
    for(int i=1<<30;i;i>>=1){
        int c=(x&i)?0:1;
        if(ch[u][c]){
            ans+=i;
            u=ch[u][c];
        }else{
            u=ch[u][!c];
        }
    return ans;
}

int main(){
    int now,ans=0;
    scanf("%d",&n;
    insert(now=0);    //先计算L[i]
    for(int i=1;i<=n;++i){
        scanf("%d",&a[i]);
        now^=a[i];
        insert(now);
        l[i]=max(l[i-1],find(now));
    }
    //倒着计算r[i]
    memset(ch,0,sizeof(ch));
    tot=1;
    insert(now=0);
    for(int i=n;i>=1;--i){
        now^=a[i];
        insert(now);
        r[i]=max(r[i+1],find(now));
    }
    for(int i=1;i<=n;++i){
        ans=max(ans,l[i]+r[i+1]);
    }
    printf("%d\n",ans);
    return 0;
}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值