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数组节点数!!!
char 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; }