01字典樹專題 (解決異或最大值問題)不斷更新ing~
阿新 • • 發佈:2018-12-24
以前一直以為字典樹沒有多少用,但是最近一直碰到(難道是以前刷題太少的原因麼),其中有一類問題叫做01字典樹問題,它是用來解決xor的有力武器,通常是給你一個數組,問你一段連續的異或和最大是多少,正常思路貪心dp啥的都會一頭霧水,但是用01字典樹就能很快的解決,實現起來也十分方便。
將要插入的數的二進位制位倒著建樹(為什麼?因為異或時高位儘量大,結果才儘量大),即高位在深度低的節點上
貼一個01字典樹的普遍模版
#define Memset(x, a) memset(x, a, sizeof(x)) typedef long long ll; const int maxn = 100000 + 5;//集合中的數字個數 int ch[32*maxn][2]; //節點的邊資訊 ll val[32*maxn]; //節點儲存的值 int sz; //樹中當前節點個數 void init(){ Memset(ch[0],0); //樹清空 sz=1; } void _insert(ll a){//在字典樹中插入 a //和一般字典樹的操作相同 將X的二進位制插入到字典樹中 int u=0; for(int i=32;i>=0;i--){ int c=((a>>i)&1); if(!ch[u][c]){ Memset(ch[sz],0); val[sz]=0; ch[u][c]=sz++; } u=ch[u][c]; } val[u]=a; //最後的節點插入value } ll query(ll a){ //在字典樹中查詢和a異或的值最大的元素b 返回b的值 int u=0; for(int i=32;i>=0;i--){ int c=((a>>i)&1); if(ch[u][c^1]) u=ch[u][c^1];//c=0,b=c^1=1,b^c=1;c=1,b=c^1=0,b^c=1; else u=ch[u][c]; } return val[u]; }
中間的細節可以自己修改,比如有時可能會刪除某個數,就需要記錄這個節點走了多少次,如果次數為0,就不往下走,陣列大小應該開32(64,如果是LL)*陣列元素個數。
廢話不多說,來看題把。
題意:O(-1)
思路:01字典樹入門題,每個數都插入字典樹中,然後查詢即可AC。
#include <bits/stdc++.h> #define Memset(x, a) memset(x, a, sizeof(x)) using namespace std; typedef long long ll; const int maxn = 100000 + 5;//集合中的數字個數 int ch[32*maxn][2]; //節點的邊資訊 ll val[32*maxn]; //節點儲存的值 int sz; //樹中當前節點個數 void init(){ Memset(ch[0],0); //樹清空 sz=1; } void _insert(ll a){//在字典樹中插入 a //和一般字典樹的操作相同 將X的二進位制插入到字典樹中 int u=0; for(int i=32;i>=0;i--){ int c=((a>>i)&1); if(!ch[u][c]){ Memset(ch[sz],0); val[sz]=0; ch[u][c]=sz++; } u=ch[u][c]; } val[u]=a; //最後的節點插入value } ll query(ll a){ //在字典樹中查詢和a異或的值最大的元素b 返回b的值 int u=0; for(int i=32;i>=0;i--){ int c=((a>>i)&1); if(ch[u][c^1]) u=ch[u][c^1];//c=0,b=c^1=1,b^c=1;c=1,b=c^1=0,b^c=1; else u=ch[u][c]; } return val[u]; } int main(){ int T,n,m; scanf("%d",&T); for(int cas=1; cas<=T; cas++){ init(); //初始化 scanf("%d%d",&n,&m); ll a; for(int i=0; i<n; i++){ scanf("%lld",&a); _insert(a); } printf("Case #%d:\n",cas); for(int i=0 ; i<m; i++){ scanf("%d",&a); printf("%lld\n",query(a)); } } return 0; }
題意:O(-1)
思路:一道01字典樹的題。將要插入的數的二進位制位倒著建樹(為什麼?因為異或時高位儘量大,結果才儘量大),即高位在深度低的節點上。用一個數組記錄經過各個節點的數的個數,插入時,每經過一個點,將節點的這個值加一,刪除時,則減一。查詢時,當前節點的這個值大於0,說明有數經過。對於要查詢的這個數的高位,如果是1,要使異或值儘量大,那麼就要往0的地方走,反之,往1的地方走,實在沒辦法走,只有按原路徑走啦。詳見程式碼。注意:0永遠在樹中。
#include<bits/stdc++.h> using namespace std; #define Memset(x, a) memset(x, a, sizeof(x)) typedef long long ll; const int maxn = 222222;//集合中的數字個數 int ch[32*maxn][2]; //節點的邊資訊 ll val[32*maxn]; //節點儲存的值 int sz,q; //樹中當前節點個數 ll num; void init(){ Memset(ch[0],0); //樹清空 sz=1; } void _insert(ll a){ int u=0; for(int i=32;i>=0;i--){ int c=((a>>i)&1); if(!ch[u][c]){ Memset(ch[sz],0); ch[u][c]=sz++; } u=ch[u][c]; ++val[u]; } } void _delete(ll a){ int u=0; for (int i=32; i>=0; --i){ int c=((a>>i)&1); u=ch[u][c]; --val[u]; } } ll query(ll a){ int u=0; ll ans=0; for(int i=32;i>=0;i--){ int c=((a>>i)&1); if (c==1){ if (ch[u][0] && val[ch[u][0]]){ ans+=1<<i; u=ch[u][0]; } else u=ch[u][1]; } else{ if (ch[u][1] && val[ch[u][1]]){ ans+=1<<i; u=ch[u][1]; } else u=ch[u][0]; } } return ans; } int main(){ ios::sync_with_stdio(false); cin.tie(0); string op; init(); cin>>q; _insert(0); while(q--){ cin>>op>>num; if(op[0]=='+')_insert(num); else if(op[0]=='-')_delete(num); else cout<<query(num)<<endl; } return 0; }
總結:
總的來說如果碰到連續異或和,一般都是01字典樹,並且01字典樹也很容易,頂多就是加個刪除,這個用加標記當前節點用過多少次,刪除的時候
num--;
,恢復的時候num++;
即可,十分簡單。xor的還有一種套路就是按位列舉的貪心構造,一般就是幾個零散的數的xor和最大或者最小,反正不要求連續的,可以往按位列舉考慮,不過按位列舉也更難。