异或树 异或+二进制Trie

额。。。有实际上是两道题但是第一道是ZZ题就不给出来了(就是把下面这个题改成有多少对点等于K然后把K和L的范围缩小到500000而已,随便玩玩异或的性质就出来了)。

而且我家题库上面的测试数据只有一个点但是T=50真的是特别不要脸ORZ

【问题描述】

  给定一棵有含有n个节点的树,定义两点之间的距离为这两点间路径上的边的异或。现在请你回答这棵树上有多少点对的异或距离小于K

【输入格式】

  第一行一个整数T表示测试数据组数。
  每组数据的第一行为n和K(树节点编号为0..n-1),然后的n-1行,每行表示一条树边的信息:u v L(0<= u,v < n,1 <= L <= 500000),表示树边连接的u,v两个结点,权值为L。

【输出格式】

每组数据输出一行,表示答案。

【输入样例】

2
4 1
0 1 1
1 2 3
2 3 2
3 0
0 1 7
0 2 7

【输出样例】

1
0

【数据范围】

2<=n<=500000
0<=K<=2^31-1
1<=L<=2^31-1

——————————————————————————————————————————————————

emmmmm……这种问题要是是两个数字之间的和的话可以强行上树的分治,但是500000的规模好像还是过不了。
简单的暴力就不多说了,直接谈谈正解。
为什么异或不可以树上分治呢?因为在十进制的基础上计算没有明显的单调性,只能开标记,但是注意K的规模发现完全不能开(开了也要因为频繁清空数组超时)。
注意,是在十进制上面没有明显的性质,那么在二进制的基础上来模拟异或的话就可以发现一些很有趣的性质了。就像十进制的加法一样,两个如果一个确定的数字和另一个不确定的数字相加要大于另一个确定的数字要怎么办呢?可以在最高位就大于另一个数字,也可以在次高位大于另一个数字。同样的想法借鉴过来,在异或的时候,在二进制中,我们考察当前节点i,要算有多少对点和这个点的异或距离小于K,我们需要生成另一个dist,这个dist的每一位都是不确定的,但是一旦确定下来会导致三种结果:当前和dist[i]异或大于、等于、小于K,我们只需要算大于等于的情况,最后把所有的点对数量算出来减去大于等于K的情况再除以2就好了。
所以就搞了个二进制的Trie,在里面是用二进制的方法记录的按照位数由高到低记录dist,Trie中每个结点记录的是这个点的所有子孙的和(即大于等于Trie到当前深度这个结点为止生成的数字的dist个数)。
嗯。。。细节交代起来太麻烦了,个人觉得精髓已经在加粗部分的比喻里面描述的很清楚了诶,就不多说了。时间复杂度是O(N),非要说的话有个31的常数。

顺道一提没有贴出来的题要用到异或x^y^y=x以及结合律的性质。

下面是代码实现:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<queue>
#include<set>
#include<map>
#include<vector>
#include<cctype>
using namespace std;
const int maxn=500005;
const int max_node=500000*31+5;
typedef long long LL;

int TT,N,K;
struct edge{ int to,next,w; }E[maxn<<1];
int first[maxn],np,dist[maxn];
struct Trie{
    int chd[max_node][2],val[max_node],fa[max_node],node,rt;
    void Initial()
    {
        node=0,rt=0;
        chd[rt][0]=chd[rt][1]=0;
    }
    void Insert(int num)//将数字num的二进制插入到Trie中
    {
        int now=rt,x=30,t=0,f=0;
        while(x>=0)
        {
            f=now;
            t=(num&(1<<x))>>x;
            if(!chd[now][t])
            {
                chd[now][t]=++node,fa[chd[now][t]]=f;
                val[chd[now][t]]=0;
                chd[chd[now][t]][0]=chd[chd[now][t]][1]=0;
            }
            now=chd[now][t];
            x--;
        }
        while(now) val[now]++,now=fa[now];
    }
    int Qeury(int num,int kk)//查询在Trie与num异或结果大于等于kk的结点数量 
    {
        int now=rt,x=30,t=0,k=0;
        int re=0;
        while(x>=0)
        {
            t=(num&(1<<x))>>x,k=(kk&(1<<x))>>x;
            if(t!=k)
            {
                if(t==1) re+=val[chd[now][0]];
                if(chd[now][1]) now=chd[now][1];
                else break;
            }
            else if(t==k)
            {
                if(t==0) re+=val[chd[now][1]];
                if(chd[now][0]) now=chd[now][0];
                else break;
            }
            x--;
        }
        if(x==-1) re+=val[now];
        return re;
    }
}T;

void _scanf(int &x)
{
    x=0;
    char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
}
void add_edge(int u,int v,int w)
{
    E[++np]=(edge){v,first[u],w};
    first[u]=np;
}
void data_in()
{
    T.Initial();
    np=0;
    memset(first,0,sizeof(first));
    _scanf(N);_scanf(K);
    int x,y,z;
    for(int i=1;i<N;i++)
    {
        _scanf(x);_scanf(y);_scanf(z);
        add_edge(x+1,y+1,z);
        add_edge(y+1,x+1,z);
    }
}
void DFS(int i,int f,int l)
{
    dist[i]=l;
    for(int p=first[i];p;p=E[p].next)
    {
        int j=E[p].to;
        if(j==f) continue;
        DFS(j,i,l^E[p].w);
    }
}
void work()
{
    DFS(1,0,0);
    for(int i=1;i<=N;i++)
        T.Insert(dist[i]);
    LL ans=0;
    for(int i=1;i<=N;i++)
        ans+=T.Qeury(dist[i],K);
    if(K==0) ans=((LL)N*N-ans)/2;
    else ans=((LL)N*N-ans-N)/2;
    cout<<ans<<endl;
}
int main()
{
    freopen("test.in","r",stdin);
    freopen("test.out","w",stdout);
    _scanf(TT);
    while(TT--)
    {
        data_in();
        work(); 
    }
    return 0;
}
    原文作者:Trie树
    原文地址: https://blog.csdn.net/qq_39439314/article/details/78145664
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞