trie字典树:初学

应用:

1.前缀问题

2.异或问题(转化为前缀问题)

3.查询问题

思想:

将要进行匹配的字符串化为一颗树

字符为边,在结束位置统计该串的全部信息

操作:插入,查询,删除.etc

ac:

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
using namespace std;
const int maxn=1e4+5;
const int Z=15;
int n,t,temp;
int trie[maxn*Z*Z][Z];//该写法调用快,但是占用空间大,视情况而定!!!trie数组节点数!!!
c
har num[Z]; bool isword[maxn*Z*Z],judge; inline void insert() { int len=strlen(num+1),pos=0; for(int i=1;i<=len;i++){ int v=trie[pos][ num[i]-'0' ]; //printf("i%d v%d\n",i,v); if(!v){//没有这个店->新建 trie[pos][num[i]-'0']=++temp; pos=temp;//!!注意不要提前给pos赋值!! // if(i==10)printf("i=10:v%d\n",v); } else pos=v;//有这个点->继续 if(isword[pos])judge=1;//该串包含某串 if(i==len){//到达字符串结尾,进行信息统计 isword[pos]=1; if(v)judge=1;//表示该串是某串的前缀 } } } int main() { scanf("%d",&t); while(t--){ memset(trie,0,sizeof(trie)); memset(isword,0,sizeof(isword)); judge=0;temp=0;//更新数据的问题 scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%s",num+1); insert(); } if(!judge)printf("YES\n"); else printf("NO\n"); } return 0; }

 

 

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<vector>
using namespace std;
/*template<typename T>
inline void read(T &a){
    a=0;T b=1;char x=getchar();
    while(x<'0'||'9'<x){
        if(x=='-')b=-1;
        x=getchar();
    }
    while('0'<=x&&x<='9'){
        a=(a<<1)+(a<<3)+x-'0';
        x=getchar();
    }
    a*=b;
}
char C[50];
int temp;
template<typename T>
inline void write(T a){
    if(a<0){
        putchar('-');
        a=-a;
    }
    do{
        C[++temp]=a%10+'0';
        a/=10;
    }while(a);
    while(temp)putchar(C[temp--]);
}*/

const int maxn=1e6+5;
int to[maxn][27],n,m,cnt,p;
vector<int >s[maxn];
char ch[15];
bool vis[maxn]; 
inline void insert(int p){
    int u=0;int len=strlen(ch+1);
    for(int i=1;i<=len;i++){
        int v=ch[i]-'a';
        if(!to[u][v])to[u][v]=++cnt;
        u=to[u][v];
        s[u].push_back(p);
    }
}
inline int query(int p){
    
    int u=0;int len=strlen(ch+1);
    for(int i=1;i<=len;i++){
        int v=ch[i]-'a';
        if(!to[u][v])return 0;
        u=to[u][v];
    }
    if(!vis[u]){sort(s[u].begin(),s[u].end());vis[u]=1;} 
    return s[u].end()-lower_bound(s[u].begin(),s[u].end(),p);
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%s%d",ch+1,&p);
        insert(p);
    }
    
    for(int i=1;i<=m;i++){
        scanf("%s%d",ch+1,&p);
        printf("%d\n",query(p));
    }
    return 0;
}
 

max xor:

思路,直接暴力很简单,但是非常慢,

发现一个性质,就是a xor b,每一位不一样的越多结果越大

所以想到trie树,我们把原来的数从32~1位建trie树,注意高位在前(不用担心会出现模式

串不够长的情况),保证结果最大,

查询时找 相反的边,实在没有就走相同的边,结果一定不会更差,

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm> 
using namespace std;
const int maxn=32e5+5;
int to[maxn][2],vis[maxn],cnt;
int n,m,num;
inline void insert(int p){
    int u=0;
    for(int i=31;i>=0;i--){//取出p的32~1位 
        bool v=p&(1<<i);
        if(!to[u][v])to[u][v]=++cnt;
        u=to[u][v];
    }
}
inline int query(int p){
    int u=0,ans=0;
    for(int i=31;i>=0;i--){//取出p的32~1位
        bool v=p&(1<<i);
        if(to[u][!v]){
            u=to[u][!v];
            ans=(ans<<1|1);
        } 
        else {
            u=to[u][v];
            ans<<=1;
        }
    }
    return ans;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&num);
        insert(num);
    }
    scanf("%d",&m);
    for(int i=1;i<=m;i++){
        scanf("%d",&num);
        printf("%d\n",query(num));
    }
    return 0;
}

 最长 xor路径:

利用了xor的与自己是互逆运算的性质,

我们维护一个数组,track[i]表示节点i到根节点的 xor值

最后每个节点询问它和已知节点(到根的xor路径值)的最大xor值

并且插入该值即可:
ac:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cstdlib>
#include<algorithm>
#include<cmath>
using namespace std;
const int maxn=32e5+5;

int first[maxn],next[maxn*2],to[maxn*2],w[maxn*2],edge_count;
inline void add(int x,int y,int z){
    edge_count++;
    to[edge_count]=y;
    w[edge_count]=z;
    next[edge_count]=first[x];
    first[x]=edge_count;
}
int track[maxn];
void dfs(int u,int fa){
    for(int i=first[u];i;i=next[i]){
        int v=to[i];
        if(v==fa)continue;
        
        track[v]=track[u]^w[i];
        dfs(v,u);
    }
}
int e[maxn][2],cnt;
inline void insert(int x){
    int u=0;
    for(int i=31;i>=0;i--){//取出32~1位 
        bool v=x&(1<<i);
        if(!e[u][v])e[u][v]=++cnt;
        u=e[u][v];
    }
}
inline int query(int x){
    int u=0;
    int ans=0;
    for(int i=31;i>=0;i--){
        bool v=x&(1<<i);
        if(e[u][v^1]){
            u=e[u][v^1];
            ans=(ans<<1|1);
        }
        else{
            u=e[u][v];
            ans<<=1;
        }
    }
    return ans;
}
int n,ans;
int main(){
    scanf("%d",&n);
    for(int i=1,u,v,q;i<n;i++){
        scanf("%d%d%d",&u,&v,&q);
        add(u,v,q);add(v,u,q);
    }
    dfs(0,0);
    for(int i=1;i<=n;i++){
        ans=max(ans,query(track[i]));
        insert(track[i]);
    }
    printf("%d",ans);
    return 0;
}

 离线处理两个串的最长前缀O(nlogn)(注意数组越界问题)

/*问题描述
给出一些串,多组询问求两个串的最长公共前缀。字符串总长 < 10^6。
输入格式
第一行一个整数n,表示字符串的个数。
接下来n行,每行一个字符串(字符串不含空格)。
第n+2行一个整数m,表示询问总数。
接下来m行,每行两个整数a,b,表示询问第a个字符串和第b个字符串的最长公共前缀的长度。
输出格式
输出共m行,对于每个询问输出最长的公共前缀的长度
输入样例
5
abcdef
abcd
acd
cade
abcdef
4
1 2
2 3
1 5
3 4
输出样例
4
1
6
0
限制与约定
1<= m < 10^6
时间限制:1s
空间限制:256MB*/
#include<cstdio>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<algorithm> 
using namespace std;
const int maxn=1e6+5;
char ch[maxn];
int n,m,to[maxn][27],cnt,vis[maxn],maxdeep;
//vis[i]表示字符串i在trie树中位置
inline void insert(int p){
    int u=0;int len=strlen(ch+1);
    maxdeep+=len;
    for(int i=1;i<=len;i++){
        int v=ch[i]-'a';
        if(!to[u][v])to[u][v]=++cnt;
        u=to[u][v]; 
    }
    vis[p]=u;
    //printf("%d%d\n",p,u);
}
int deep[maxn],f[maxn][30],log[maxn]; 
void build(int u){
    for(int i=1;i<=log[ deep[u] ];i++)
        f[u][i]=f[f[u][i-1]][i-1];
    for(int i=0;i<=27;i++){
        if(to[u][i]){
        deep[ to[u][i] ]=deep[u]+1;
        f[ to[u][i] ][0]=u;
        
        build(to[u][i]);
        }
    }
}
inline void LCA_init(){
    for(int i=2;i<=maxdeep;i++)log[i]=log[i>>1]+1;
    deep[0]=1;//??非常关键的地方,以谁为根谁的深度就是1 
    build(0);
}
inline int LCA(int x,int y){
    if(deep[x]<deep[y])swap(x,y);
    for(int i=log[deep[x]];i>=0;i--){
        if(deep[y]<=deep[f[x][i]])x=f[x][i];
    }
    if(x==y)return x;
    for(int i=log[deep[x]];i>=0;i--){
        if(f[x][i]!=f[y][i]){
            x=f[x][i];y=f[y][i];
        }
    }
    return f[x][0];
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%s",ch+1);
        insert(i);
    }
    scanf("%d",&m);
    LCA_init();
    for(int i=1,a,b;i<=m;i++){
        scanf("%d%d",&a,&b);
        printf("%d\n",deep[LCA(vis[a],vis[b])]-1);
    }
    return 0;
}
/*问题描述
给出一些串,多组询问求两个串的最长公共前缀。字符串总长 < 10^6。
输入格式
第一行一个整数n,表示字符串的个数。
接下来n行,每行一个字符串(字符串不含空格)。
第n+2行一个整数m,表示询问总数。
接下来m行,每行两个整数a,b,表示询问第a个字符串和第b个字符串的最长公共前缀的长度。
输出格式
输出共m行,对于每个询问输出最长的公共前缀的长度
输入样例
5
abcdef
abcd
acd
cade
abcdef
4
1 2
2 3
1 5
3 4
输出样例
4
1
6
0
限制与约定
1<= m < 10^6
时间限制:1s
空间限制:256MB*/
#include<cstdio>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<algorithm> 
using namespace std;
const int maxn=1e6+5;
char ch[maxn];
int n,m,to[maxn][27],cnt,vis[maxn],maxdeep;
//vis[i]表示字符串i在trie树中位置
inline void insert(int p){
    int u=0;int len=strlen(ch+1);
    maxdeep+=len;
    for(int i=1;i<=len;i++){
        int v=ch[i]-'a';
        if(!to[u][v])to[u][v]=++cnt;
        u=to[u][v]; 
    }
    vis[p]=u;
    //printf("%d%d\n",p,u);
}
int deep[maxn],f[maxn][30],log[maxn]; 
void build(int u){
    for(int i=1;i<=log[ deep[u] ];i++)
        f[u][i]=f[f[u][i-1]][i-1];
    for(int i=0;i<=27;i++){
        if(to[u][i]){
        deep[ to[u][i] ]=deep[u]+1;
        f[ to[u][i] ][0]=u;
        
        build(to[u][i]);
        }
    }
}
inline void LCA_init(){
    for(int i=2;i<=maxdeep;i++)log[i]=log[i>>1]+1;
    deep[0]=1;//??非常关键的地方,以谁为根谁的深度就是1 
    build(0);
}
inline int LCA(int x,int y){
    if(deep[x]<deep[y])swap(x,y);
    for(int i=log[deep[x]];i>=0;i--){
        if(deep[y]<=deep[f[x][i]])x=f[x][i];
    }
    if(x==y)return x;
    for(int i=log[deep[x]];i>=0;i--){
        if(f[x][i]!=f[y][i]){
            x=f[x][i];y=f[y][i];
        }
    }
    return f[x][0];
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%s",ch+1);
        insert(i);
    }
    scanf("%d",&m);
    LCA_init();
    for(int i=1,a,b;i<=m;i++){
        scanf("%d%d",&a,&b);
        printf("%d\n",deep[LCA(vis[a],vis[b])]-1);
    }
    return 0;
}

用于优化dp:

/*问题描述
给出一个由S个不同的单词组成的字典和一个长字符串。把这个字符串分解成若干个单词的连接(单词可以重复使用),有多少种方法?
输入格式
输入包含多组数据。每组数据第一行为小写字母组成的待分解字符串,长度L不超过300 000,紧接着是单词个数S(1<=S<=4000)。第二行为S个单词,单词中间用空格隔开
S个单词由不超过100个小写字母组成。输入结束标志为文件结束符(EOF)。
输出格式
对于每组输出数据,输出分解方案数除以20071027的余数。
样例输入
abcd 4
a b cd ab
样例输出
Case 1: 2
样例解释:
方案1:abcd=a+b+cd; 方案二:abcd=ab+cd。
限制与约定
时间限制:1s
空间限制:128MB*/
#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<cstdlib>
#include<algorithm>
using namespace std;
template<typename T>
inline void read(T &a){
    a=0;bool b=0;char x=getchar();
    while(x<'0'||'9'<x){
        if(x=='-')b=1;
        x=getchar();
    }
    while('0'<=x&&x<='9'){
        a=(a<<1)+(a<<3)+x-'0';
        x=getchar();
    }
    if(b)a=-a;
}
char C[50];
int temp;
template<typename T>
inline void write(T a){
    if(a<0){
        putchar('-');
        a=-a;
    }
    do{
        C[++temp]=a%10+'0';
        a/=10;
    }while(a);
    while(temp)putchar(C[temp--]);
}
const int maxn=4e5+5;
const int P=20071027;
int to[maxn][27],cnt,f[maxn];
char target[maxn],a[maxn];
bool vis[maxn];
inline void insert(){
    int u=0;
    for(int i=strlen(a+1);i;i--){//注意倒序插入
        int v=a[i]-'a';
        if(!to[u][v])to[u][v]=++cnt;
        u=to[u][v];
    }
    vis[u]=1;
}
inline void query(int p){
    //printf("%d:",p);
    int u=0;
    for(int i=0;i<=100;i++){//找位置p及之前的99位
        if(p==i)break;
        int v=target[p-i]-'a';
        if(!to[u][v])break;
        
        //printf("v%d ",v);
        
        u=to[u][v];
        if(vis[u])f[p]=(f[p]+f[p-i-1])%P;
    }
    //printf("\n");
}
int s;
inline void clear(){
    memset(to,0,sizeof(to));
    cnt=0;
    memset(f,0,sizeof(f));
    memset(vis,0,sizeof(vis));
}
int main(){
    int tt=0;
    while(scanf("%s",target+1)!=EOF){
        tt++;
        clear();
        
        scanf("%d",&s);
        while(s--){
            scanf("%s",a+1);
            insert();
        }
        int len=strlen(target+1);
        f[0]=1;
        for(int i=1;i<=len;i++)query(i);
        printf("Case %d: %d\n",tt,f[len]);
    }
    return 0;
}

 

问题描述

给定 一个含N个元素的数组A,下标从1开始,请找出下面式子的最大值: (A[L1]^ A[L1+1]^…^ A[R1])+ (A[L2]^ A[L2+1]^…^ A[R2])。其中1<= L1<= R1< L2<= R2<=N。式子中x^y表示x和y的按位异或运算。

输入格式

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

输出格式

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

输入样例

5
1 2 3 1 2

输出样例

6

限制与约定

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

对于100%的数据,2 ≤ N ≤ 4*10^5,0 ≤ Ai ≤ 10^9。

时间限制:1s

空间限制:256

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<cstdlib>
#include<algorithm>
#include<vector>
using namespace std;
template<typename T>
inline void read(T &a){
    a=0;bool b=0;char x=getchar();
    while(x<'0'||'9'<x){
        if(x=='-')b=1;
        x=getchar();
    }
    while('0'<=x&&x<='9'){
        a=(a<<1)+(a<<3)+x-'0';
        x=getchar();
    }
    if(b)a=-a;
}
char C[50];
int temp;
template<typename T>
inline void write(T a){
    if(a<0){
        putchar('-');
        a=-a;
    }
    do{
        C[++temp]=a%10+'0';
        a/=10;
    }while(a);
    while(temp)putchar(C[temp--]);
}
/*
main thought:预处理出prexor[i]表示位置a[1]^……^a[i]的xor值,
以及premax[i]表示max(prexor[j]~prexor[k]),由于递增,O(n)求出
最后扫描一遍即可
*/
const int maxn=4e5+5;
int to[maxn*32][2],cnt;//空间别算错,二叉树
inline void insert(int num){
    int u=0;
    for(int i=31;i>=0;i--){//倒序插入,32~0位
        bool v=num&(1<<i);//取出
        if(!to[u][v])to[u][v]=++cnt;
        u=to[u][v];
    }
}
inline int query(int num){
    int ans=0,u=0;
    for(int i=31;i>=0;i--){
        bool v=num&(1<<i);//取出该位&
        if(to[u][v^1]){
            u=to[u][v^1];
            ans=(ans<<1|1);
        }
        else {
            u=to[u][v];
            ans<<=1;
        }
    }
    return ans;
}
int prexor[maxn],maxpre[maxn],n,a[maxn],ans,now;
inline void clear(){
    memset(to,0,sizeof(to));
    cnt=0;
}
int main(){
    read(n);
    insert(0);//记得插入0
    for(int i=1;i<=n;i++){
        read(a[i]);
        prexor[i]=prexor[i-1]^a[i];
        insert(prexor[i]);
        maxpre[i]=max(maxpre[i-1],query(prexor[i]));
    }
    clear();
    insert(0);//记得插入0
    for(int i=n;i;i--){
        now^=a[i];
        insert(now);
        ans=max(ans,maxpre[i-1]+query(now));
    }
    write(ans);
    return 0;
}

 

posted @ 2019-04-27 22:25  Tj1  阅读(122)  评论(0编辑  收藏  举报