4575 Tree(可持久化字典樹)
阿新 • • 發佈:2019-01-28
題意:給出一顆樹,每棵樹上有n個結點,每個結點對應一個值,有m組查詢操作,查詢在從x到y的這條路徑上與z進行異或的最大值。
分析:可持久化字典樹的模板題
這個題以01字典樹為基礎,如果不是很瞭解01字典樹的話,可以看看ACdream1063,這題題解。
話說回來,如果我們要查詢數x與某個數異或的最大值,我們應該儘量選擇高位與x的高位相反的一些數。
舉個栗子,我們要求5與某個數異或的最大值,假設可選的數不超過15,5的二進位制為0101,因為可選的數不超過15,所以我們只要考慮後四位,
首先看最高位(第四位)存不存在等於1的數,存在的話,一直往低位走,最終可以得到結果。
這個題就要對每個結點都建一棵Trie樹(感覺根主席樹有點像噢,主席樹是對每個結點建線段樹
建Trie樹的時候,我們用一個數組sz[]記錄字典樹對應結點字首的數量,如果v是u的子節點,且v的權值是010,u的權值為011,假設u已經加到樹中,那麼在加v的時候,發現字首01的數量已經是1了,那我們只要在這個基礎上加1即可,也就是繼承了父節點對它的影響,但是對於010來說是一個新的數,我們要新建一個結點然後更新他的sz,其餘和父節點一致就好。
當我們去查詢每一位都取反的x時,只關心每一位是否對應存在。也就是想知道u到v這條路上有沒有滿足條件的存在。
設pre=lca(u,v),我們就只要判斷f(u)+f(v)-2×f(pre)是否大於0就好,大於0的話,就加上1>>i。
參考程式碼:
/*可持久化字典樹*/ #include<cstdio> #include<cmath> #include<cstring> #include<algorithm> #include<vector> #include<iostream> using namespace std; #define mem(a,b) memset(a,b,sizeof(a)) const int maxn = 1e5+10;//原樹結點 const int maxv = 16;//每顆字典樹的深度 const int maxnode = 2e6;//字典樹的結點個數,16*1e5=1600000 int n,q; int a[maxn]; vector<int> g[maxn]; //字典樹 int tot;//記錄字典樹的總結點數 int root[maxn];//記錄字典樹的根節點 int f[maxv+2][maxn];//嘻嘻嘻嘻f[i][v]指的是結點v向上跳2^i次後得到的結點值 int sz[maxnode];//記錄字首和 int dep[maxn];//記錄結點深度 int ch[maxnode][2];//字典樹兒子結點 void Init() { tot = 0; mem(root,0); mem(f,0); mem(sz,0);//字首和初始化為0 dep[0] = 0; } int NewNode() { mem(ch[++tot],0); return tot; } void Insert( int u, int fa, int val) { int rtu = root[u];//root[u]是字典樹中真正的位置,而u是在原樹中的編號 int rtf = root[fa];//同理 for( int i = 15; i >= 0; i--) { int id = (val>>i)&1;//計算val的第i位(有第0位)是1還是0 if( !ch[rtu][id])//如果第i位不存在的話,那就要新建一個結點 { ch[rtu][id] = NewNode(); ch[rtu][!id] = ch[rtf][!id];//!id就繼承父節點的 sz[ch[rtu][id]] = sz[ch[rtf][id]];//字首也繼承父節點的 } rtu = ch[rtu][id];//往下走 rtf = ch[rtf][id]; sz[rtu]++; } } //遍歷原樹中的n個結點,對每個結點建立01字典樹 void dfs( int u, int fa) { f[0][u] = fa;//記錄下u的父節點 dep[u] = dep[fa]+1;//當前結點的深度為其父親的深度+1 root[u] = NewNode();//給字典樹新建一個結點 Insert(u,fa,a[u]);//根據結點u對應的值建01字典樹 for( int i = 0; i < g[u].size(); i++)//遍歷當前結點u的葉子結點 { int v = g[u][i]; if( v != fa)//如果不是其父節點,繼續深搜 dfs(v,u); } } //倍增lca //求u和v的最近公共祖先 int lca( int u, int v) { if( dep[u] > dep[v])//保證u的深度小於v,也就是u和v的祖先結點在同一深度 swap(u,v); for( int i = 0; i < maxv; i++) if( ((dep[v]-dep[u])>>i) & 1)//如果條件為真,就往上跳,最終結果會與u在同一層 v = f[i][v]; if( u == v)//顯然 return u; for( int i = maxv-1; i >= 0; i--) if( f[i][u] != f[i][v])//還不相等的話,就一起往上走 u = f[i][u], v = f[i][v]; return f[0][u];//最後u==v,返回自己 } int Query( int u, int v, int val) { int f = lca(u,v);//求u,v的最近公共祖先 int res = a[f]^val; int rtu = root[u], rtv = root[v], rtf = root[f];//轉化到字典樹中去 int ret = 0; for( int i = maxv-1; i >= 0; i--) { int id = (val>>i)&1; if( sz[ch[rtu][!id]]+sz[ch[rtv][!id]]-2*sz[ch[rtf][!id]] > 0)//滿足這個條件 { ret += 1<<i; id = !id; } rtu = ch[rtu][id]; rtv = ch[rtv][id]; rtf = ch[rtf][id]; } return max(ret,res); } int main() { while( ~scanf("%d%d",&n,&q)) { Init(); for( int i = 1; i <= n; i++) { scanf("%d",&a[i]);//輸入每個結點的值 g[i].clear();//清空動態陣列 } int u,v; for( int i = 1; i < n; i++) { scanf("%d%d",&u,&v);//輸入一條邊 g[u].push_back(v); g[v].push_back(u); } dfs(1,0); for( int i = 0; i < maxv-1; i++) for( int j = 1; j <= n; j++) if( f[i][j] != 0) f[i+1][j] = f[i][f[i][j]];//j向上跳2^(i+1)次得到的結點相當於j先向上跳2^i再向上跳2^i int x,y,z; while( q--) { scanf("%d%d%d",&x,&y,&z); printf("%d\n",Query(x,y,z)); } } return 0; }