字典树大家都知道吧,如果不知道可以看这里,我的模板写得还是不错的:
字符串–Trie树(字典树)
接下来我们先看一个问题,通过这个问题来了解Xor这个运算的基本性质:
洛谷 P2420 让我们异或吧
题目大意:给定一棵带权树,多次询问两点之间边权异或和。
性质:a^x^x=a,其中^代表Xor运算,证明略。
利用这条性质,我们可以任取一点为根,通过一次dfs算出每个点到根节点的异或和val[u],假设每次询问的点是u,v,则答案就是val[u]^val[v]。
过程: val(u,v)
=val(u,lca) Xor val(v,lca)
=val(u,root) Xor val(lca,root) Xor val(v,root) Xor val(lca,root)
=val(u,root) Xor val(v,root)
此题代码:
//Âå¹È2420 ÈÃÎÒÃÇÒì»ò°É
#include
#include
#define ll long long
using namespace std;
inline int read(){
int x=0;char ch=' ';
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return x;
}
struct edge{
int to,next,w;
}e[200001];
int n,m,tot;
int head[100001];
int v[100001];
int st[100001][20];
int dep[100001];
void dfs(int x,int fa){
st[x][0]=fa;
for(int i=head[x];i;i=e[i].next){
int u=e[i].to;
if(u==fa)continue;
v[u]=v[x]^e[i].w;
dep[u]=dep[x]+1;
dfs(u,x);
}
}
inline void addedge(int x,int y,int l){
e[++tot].to=y;e[tot].next=head[x];e[tot].w=l;head[x]=tot;
}
int main(){
n=read();
for(int i=1;i<=n-1;i++){
int x=read(),y=read(),l=read();
addedge(x,y,l);addedge(y,x,l);
}
dfs(1,0);
m=read();
for(int i=1;i<=m;i++){
int x=read(),y=read();
int ans=v[x]^v[y];
printf("%d\n",ans);
}
return 0;
}
接下来就是正题了——如何用字典树来解决Xor问题?
题目链接:Xor Sum
整体思路:Trie树+贪心
我们要选出一个数来使它和另一个数的异或和最大,那么当然要转二进制来看了。
转了二进制之后我们可以发现高位的值比所有低位的值相加还要大,那么就有贪心思路了:
建立一棵01Trie(每个点只有0和1两个儿子),然后将每个初始数字从高位到低位按位插入进来,然后每次查询时每一步尽量走和它那一位不同的方向,最后输出即可。
此题代码:
//hdu4825 Xor Sum
#include
#include
#define ll long long
using namespace std;
inline int read(){
int x=0;char ch=' ';
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return x;
}
int n,m,tot;
int ch[3100001][2];
int val[3100001];
void insert(int x){
int u=0;
for(int i=31;i>=0;i--){
int c=(x>>i)&1;
if(!ch[u][c]){
ch[u][c]=++tot;
}
u=ch[u][c];
}
val[u]=x;
}
int find(int x){
int u=0;
for(int i=31;i>=0;i--){
int c=(x>>i)&1;
if(ch[u][c^1]){
u=ch[u][c^1];
}
else{
u=ch[u][c];
}
}
return val[u];
}
int main(){
int T=read();
int k=1;
while(T--){
printf("Case #%d:\n",k++);
memset(ch,0,sizeof(ch));
memset(val,0,sizeof(val));
tot=0;
n=read();m=read();
for(int i=1;i<=n;i++){
insert(read());
}
for(int i=1;i<=m;i++){
printf("%d\n",find(read()));
}
}
return 0;
}
接下来我们将第一个问题和第二个问题结合在一起来看一个题;
题目链接:The xor-longest Path
题目大意:给定一棵树,求最大异或路径。
思路:我们可以每次固定一个点,寻找和它异或路径最大的点,更新答案。这时问题就转化成了在树上已知一个点如何找另一个点和它异或路径最大。
结合T1和T2,那么我们可以表示出两点间的异或路径:val(u)^val(v),其中因为我们已经固定了一个点了,那么就是要找一个值和已知值异或值最大了,这就是T2的问题。
方案就很明显了,dfs求出每个点到根节点的异或值,然后将这些值插入到一棵01Trie中,枚举每个点找从这个点出发的最大异或路径,并更新答案。
此题代码:
//poj3764 The xor-longest Path
#include
#include
#include
#define ll long long
using namespace std;
int v[100001];
int ch[3100001][2];
int n;
struct edge{
int to,next,w;
}e[200001];
int head[100001];
void dfs(int x,int fa){
for(int i=head[x];i;i=e[i].next){
int u=e[i].to;
if(u==fa)continue;
v[u]=v[x]^e[i].w;
dfs(u,x);
}
}
int tot;
inline void addedge(int x,int y,int l){
e[++tot].to=y;
e[tot].next=head[x];
head[x]=tot;
e[tot].w=l;
}
void insert(int x){
int u=0;
for(int i=30;i>=0;i--){
int c=(x>>i)&1;
if(!ch[u][c]){
ch[u][c]=++tot;
}
u=ch[u][c];
}
}
int find(int x){
int u=0;int ans=0;
for(int i=30;i>=0;i--){
int c=(x>>i)&1;
if(ch[u][c^1]){
ans+=(1<1];
}
else{
u=ch[u][c];
}
}
return ans;
}
int main(){
while(scanf("%d",&n)==1){
memset(ch,0,sizeof(ch));
memset(v,0,sizeof(v));
memset(head,0,sizeof(head));
tot=0;
for(int i=1;i<=n-1;i++){
int x,y,l;
scanf("%d %d %d",&x,&y,&l);
addedge(x,y,l);
addedge(y,x,l);
}
dfs(0,-1);
for(int i=0;i0;
int ans=0;
for(int i=0;iprintf("%d\n",ans);
}
return 0;
}