HDU 4757 Tree (倍增演算法求LCA + 可持久化Trie樹)
阿新 • • 發佈:2019-01-23
題目大意:
就是現在給出一棵樹, 結點個數不超過10W, 每個節點上有一個不超過2^16的非負整數, 然後10W次詢問, 每次詢問兩個節點的路徑上的所有數中異或上給出的數的最大值
大致思路:
剛開始做這個題想的是樹鏈剖分之後用線段樹套Trie樹來做...結果悲劇地MLE了...
另外那樣做得話複雜度其實是比較大的...每次詢問都是16*(logn)*logn級別的..
後來發現是個可持久化Trie樹...
表示第一次寫可持久化Trie樹, 感覺和主席樹很像, 都是多個版本的Trie的集合體, 然後由要訪問的版本的不同而從不同的根節點出發來訪問
對於給出的樹按照父親和兒子的關係來建立可持久化Trie樹
每次訪問的時候訪問到的Trie樹版本是考慮了從當前結點到父親結點的路徑上的所有點的一顆Trie樹, 那麼對於每次的路徑(u, v)的查詢, 就是u開始的Trie與v開始的Trie的查詢減去他們的LCA的查詢, 然後利用貪心的思想求出可能的最大值即可
程式碼如下:
Result : Accepted Memory : 41812 KB Time : 1528 ms
/* * Author: Gatevin * Created Time: 2015/10/2 15:20:38 * File Name: Sakura_Chiyo.cpp */ #include<iostream> #include<sstream> #include<fstream> #include<vector> #include<list> #include<deque> #include<queue> #include<stack> #include<map> #include<set> #include<bitset> #include<algorithm> #include<cstdio> #include<cstdlib> #include<cstring> #include<cctype> #include<cmath> #include<ctime> #include<iomanip> using namespace std; const double eps(1e-8); typedef long long lint; #define maxn 100010 struct Edge { int u, v, nex; Edge(int _u, int _v, int _nex) { u = _u, v = _v, nex = _nex; } Edge(){} }; Edge edge[maxn << 1]; int head[maxn]; int tot; void add_Edge(int u, int v) { edge[++tot] = Edge(u, v, head[u]); head[u] = tot; } int w[maxn]; int father[maxn][20]; int dep[maxn]; int n, m; /* * 可持久化Trie樹 */ struct Trie { #define maxnode 2800100 int next[maxnode][2]; int L; int root[maxn]; int sz[maxnode]; void init() { L = 0; memset(root, 0, sizeof(root)); memset(sz, 0, sizeof(sz)); } int newnode() { next[L][0] = next[L][1] = -1; L++; return L - 1; } /* * 一個以root[x]開始的Trie樹是樹上x向上到鏈的陣列成的Trie樹 * 然後樹上每個節點都用sz[u][v]表示以u向上到根的值組成的Trie樹中結點v代表的出現次數 */ void insert(int x, int fa, int value)//x的父親節點是y { x = root[x]; int y = root[fa]; for(int i = 15; i >= 0; i--) { int nex = 0; if(value & (1 << i)) nex = 1; if(next[x][nex] == -1) { int id = newnode(); next[x][nex] = id; next[x][nex^1] = next[y][nex^1];//合併 sz[next[x][nex]] = sz[next[y][nex]]; } x = next[x][nex], y = next[y][nex]; ++sz[x]; } } int query(int x, int y, int z, int value)//z是x和y的lca { int ret = 0, ano = w[z]; x = root[x], y = root[y], z = root[z]; for(int i = 15; i >= 0; i--) { int nex = 0; if(value & (1 << i)) nex = 1; if(sz[next[x][nex ^ 1]] + sz[next[y][nex ^ 1]] - 2*sz[next[z][nex ^ 1]] > 0)//說明在[x, y]的鏈上有數提供這樣的路走 { x = next[x][nex ^ 1], y = next[y][nex ^ 1], z = next[z][nex ^ 1]; ret += (1 << i); } else { x = next[x][nex], y = next[y][nex], z = next[z][nex]; } } return max(ret, ano ^ value); } }; Trie trie; void dfs(int now) { trie.root[now] = trie.newnode(); trie.insert(now, father[now][0], w[now]);//按照樹的列的順序插入 for(int i = head[now]; i + 1; i = edge[i].nex) { if(edge[i].v == father[now][0]) continue; father[edge[i].v][0] = now; dep[edge[i].v] = dep[now] + 1; dfs(edge[i].v); } return; } int swim(int x, int H) { for(int i = 0; H > 0; i++) { if(H & 1) x = father[x][i]; H >>= 1; } return x; } int lca(int x, int y) { int i; if(dep[x] < dep[y]) swap(x, y); int k = dep[x] - dep[y]; x = swim(x, k); if(x == y) return x; while(1) { for(i = 0; father[x][i] != father[y][i]; i++); if(i == 0) return father[x][0]; x = father[x][i - 1]; y = father[y][i - 1]; } return -1; } void answer(int x, int y, int z) { printf("%d\n", trie.query(x, y, lca(x, y), z)); } bool get(int& x) { x = 0; char c; bool ined = 0; while(!ined) { c = getchar(); if(c == EOF) break; while(c >= '0' && c <= '9') { ined = 1; x = (x << 3) + (x << 1) + c - '0'; c = getchar(); } } return ined; } int main() { while(get(n)) { get(m); memset(head, -1, sizeof(head)); tot = 0; for(int i = 1; i <= n; i++) get(w[i]); int u, v; for(int i = 1; i < n; i++) { get(u); get(v); add_Edge(u, v); add_Edge(v, u); } trie.init(); memset(father, 0, sizeof(father)); dep[1] = 1; father[1][0] = -1; dfs(1); for(int j = 1; (1 << j) <= n; j++) for(int i = 1; i <= n; i++) if(father[i][j - 1] != -1) father[i][j] = father[father[i][j - 1]][j - 1];//準備好求lca while(m--) { int x, y, z; scanf("%d %d %d", &x, &y, &z); answer(x, y, z); } } return 0; }