字典树适用于处理一些字符串问题(某些时候跟哈希的用处有点像),还有一些异或问题。
先把名单上的所有名字存放到字典树里,每念一个名字查找一下,字典树末端指针指向的结点中的num用于记录点到这里的次数,这样每次到结尾时根据num可以判断是否重复,如果不能顺利走到尾的话说明念错了。
#include
using namespace std;
struct Node
{
int son[30];
int num;
}node[500010];
int n,m,now;
void insert(string s)
{
int p=0,len=s.size();
for(int i=0;i<len;i++)
{
if(node[p].son[s[i]-'a']==0)
node[p].son[s[i]-'a']=++now;
p=node[p].son[s[i]-'a'];
}
}
int find(string s)
{
int p=0,len=s.size();
for(int i=0;i<len;i++)
{
if(node[p].son[s[i]-'a']==0)
return 0;
p=node[p].son[s[i]-'a'];
}
node[p].num++;
return node[p].num;
}
int main()
{
cin>>n;
for(int i=0;i<n;i++)
{
string str;
cin>>str;
insert(str);
}
cin>>m;
for(int i=0;i<m;i++)
{
string name;
cin>>name;
int ans=find(name);
//cout<<"ans:"<
if(ans==1) printf("OK\n");
if(ans==0) printf("WRONG\n");
if(ans>=2) printf("REPEAT\n");
}
return 0;
}
看题目的描述很容易联想到当时学的归并排序求逆序对数,但是这里并不是一些有大小顺序的数而是很多字符串,怎么办呢?
f ( s t r i n g ) − > n u m b e r f(string)->number f(string)−>number
我们可以采取很多种映射的办法,最直接的是map,其他的一些比如哈希等,这里采用字典树,插入时走到字符串末尾时给每个字符串一个编号,重排之后原有的顺序被打乱,归并排序的时候顺便求一下逆序对数就好啦。
#include
using namespace std;
const int N=5e5+10;
int n,tot,trie[N][100],a[N],b[N],temp[N];
long long ans;
void insert(string s,int x)
{
int p=0,len=s.size();
for(int i=0;i<len;i++)
{
if(!trie[p][s[i]-'A'])
trie[p][s[i]-'A']=++tot;
p=trie[p][s[i]-'A'];
}
a[p]=x;
}
void query(string s,int x)
{
int p=0,len=s.size();
for(int i=0;i<len;i++)
p=trie[p][s[i]-'A'];
b[x]=a[p];
}
void merge_sort(int l,int r)
{
if(l==r) return;
int mid=(l+r)>>1;
merge_sort(l,mid);
merge_sort(mid+1,r);
int i=l,j=mid+1,k=l;
while(i<=mid&&j<=r)
{
if(b[i]<=b[j]) temp[k++]=b[i++];
else temp[k++]=b[j++],ans+=mid-i+1;
}
while(i<=mid) temp[k++]=b[i++];
while(j<=r) temp[k++]=b[j++];
for(int p=l;p<=r;p++) b[p]=temp[p];
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
string name;
cin>>name;
insert(name,i);
}
for(int i=1;i<=n;i++)
{
string name;
cin>>name;
query(name,i);
}
//for(int i=1;i<=n;i++)
//cout<
merge_sort(1,n);
cout<<ans;
return 0;
}
以上两题是在字典树中插入字符串。如果要解决异或问题的话,实际上是把二进制数看作01串插到字典树中,靠近树梢的一般是较高位,它们权重比较大,如果位高权重的说话了,靠近树梢的就可以闭嘴了>_<(bushi)
暴力的做法是对于每一个数,枚举其他所有数,求出(n-1)个异或值,有了字典树之后可以快速找到跟当前数异或值最大的那个。把这些01串都插入字典树,然后枚举这n个数,在这n个数对应找出的异或值中选最大的就行了。
怎么找到一个数对应的最大异或值呢?从高位(树根)开始每次都进入反子树(比如101011,第一次进入0子树,第二次进入1子树,第三次进入0子树,以此类推),如果想要进入的子树为空的话就迫不得已进入同子树。这样可以尽可能地选取到异或值大的数。每向下移一层,异或值乘2,如果进入反子树异或值加1。
#include
using namespace std;
const int N=1e5+10;
int n,trie[N*32][2],tot,a[N];
int maxn=0;
inline int read()
{
int x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x*f;
}
void insert(int x)
{
int p=0;
for(int i=31;i>=0;i--)
{
int t=(x>>i)&1;
if(!trie[p][t])
trie[p][t]=++tot;
p=trie[p][t];
}
}
int query(int x)
{
int q=0,ans=0,t;
for(int i=31;i>=0;i--)
{
t=(x>>i)&1;//取出当前位
if(!trie[q][!t])//反子树为空
{
q=trie[q][t];
ans<<=1;
}
else
{
q=trie[q][!t];//进入反子树
ans<<=1;
ans+=1;//0^1=1
}
}
return ans;
}
int main()
{
n=read();
for(int i=0;i<n;i++)
{
a[i]=read();
insert(a[i]);
}
for(int i=0;i<n;i++)
{
int temp=query(a[i]);
maxn=max(maxn,temp);
}
cout<<maxn;
return 0;
}
设a[x]表示根结点到x的路径上所有边权的异或值
a [ x ] = a [ f a t h e r ( x ) ] w e i g h t ( x , f a t h e r ( x ) ) a[x]=a[father(x)]^weight(x,father(x)) a[x]=a[father(x)]weight(x,father(x))
可以先DFS求出所有的a[x],则任意两个结点x,y路径上所有边权的异或值就等于a[x]^a[y],问题转化成从a[1]~a[n]这n个数中选两个,求最大异或值,同上一道题。
#include
using namespace std;
const int N=1e5+10;
int n,nex[N*2],head[N*2],val[N*2],a[N],to[N*2],k;
int trie[N<<4][2],tot;
int maxn=0;
inline int read()
{
int x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x*f;
}
void add(int x,int y,int w)
{
to[++k]=y;
nex[k]=head[x];
head[x]=k;
val[k]=w;
}
void dfs(int x,int fa)
{
for(int i=head[x];i;i=nex[i])
{
int y=to[i];
if(y==fa) continue;
a[y]=(val[i]^a[x]);
dfs(y,x);
}
}
void insert(int x)
{
int p=0;
for(int i=31;i>=0;i--)
{
int t=(x>>i)&1;
if(!trie[p][t])
trie[p][t]=++tot;
p=trie[p][t];
}
}
int query(int x)
{
int q=0,ans=0,t;
for(int i=31;i>=0;i--)
{
t=(x>>i)&1;
if(!trie[q][!t])
{
q=trie[q][t];
ans<<=1;
}
else
{
q=trie[q][!t];
ans<<=1;
ans+=1;
}
}
return ans;
}
int main()
{
cin>>n;
for(int i=1;i<n;i++)
{
int x=read(),y=read(),w=read();
add(x,y,w);
add(y,x,w);
}
a[1]=0;
dfs(1,0);
for(int i=1;i<=n;i++)
insert(a[i]);
for(int i=1;i<=n;i++)
{
int temp=query(a[i]);
maxn=max(maxn,temp);
}
cout<<maxn;
return 0;
}
聪明值不会超过 2 21 2^{21} 221,我们在此范围内枚举每一个数x,在字典树中进行查找,求出x^k的每个二进制位,枚举每一位时,反子树内的结点个数可以直接被统计上(最后必然符合条件),一直枚举到最后一位(注意结尾要特判一下,因为是<=k)。
#include
using namespace std;
const int N=1e6+10;
int n,k,a[N],now,ans,sz[N*21],trie[N*21][2],tot;
inline int read()
{
int x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x*f;
}
void insert(int x)
{
int p=0;
for(int k=21,c;k>=0;k--)
{
c=(x>>k)&1;
if(!trie[p][c]) trie[p][c]=++tot;
p=trie[p][c];
sz[p]++;//记录有多少个数以到目前为止的这一串“01”为前缀
}
}
int query(int x)
{
int ret=0,p=0;
bool flag=0;
for(int i=21,c;i>=0;i--)
{
c=(x>>i)&1;//x这一位的值
int t=(k>>i)&1;//k这一位的值
if(t==1) ret+=sz[trie[p][1-c^t]];//如果k这一位的值是1,那么这一位取1-(c^t)的时候一定可行,反之一定不可行
if(!trie[p][c^t]) {flag=1;break;}//如果走不到就返回
p=trie[p][c^t];
}
if(!flag) ret+=sz[p];//走到最底层侧了,这里对应的数就是k
return ret;
}
int main()
{
n=read(),k=read();
for(int i=0;i<n;i++)
{
a[i]=read();
insert(a[i]);
}
int temp;
for(int i=0;i<(1<<21);i++)
{
ans=max(ans,query(i));
if(ans==n) break;
}
cout<<ans;
return 0;
}