異或樹 異或+二進制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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章