求编号在区间[l, r]之间的两两lca的深度最大值。
例题。
解:口胡几种做法。前两种基于莫队,第三种是启发式合并 + 扫描线,第四种是lct + 线段树。
①:
有个结论就是这个答案一定是点集中DFS序相邻的两个点的lca。于是开个数据结构,以DFS序为key维护点集,找前驱后继,额外用一个数据结构维护所有lca的深度,取最大值即可。外面套莫队就做完了。
实现上这两个数据结构都可以用树状数组。
1 #include <bits/stdc++.h> 2 3 #define out(a) std::cerr << #a" = " << a << std::endl; 4 5 template <class T> inline void read(T &x) { 6 x = 0; 7 char c = getchar(); 8 while(c < '0' || c > '9') c = getchar(); 9 while(c >= '0' && c <= '9') { 10 x = x * 10 + c - 48; 11 c = getchar(); 12 } 13 return; 14 } 15 16 const int N = 80010; 17 18 struct Edge { 19 int nex, v; 20 }edge[N << 1]; int tp; 21 22 int e[N], pos[N], pos2[N], num, num2, fr[N], ans[N], id[N], ST[N << 1][20], d[N], pw[N << 1], n; 23 /*std::set<int> st; /// save pos 24 std::set<int>::iterator it;*/ 25 /*std::multiset<int> Ans; 26 std::multiset<int>::iterator it2;*/ 27 28 namespace Ans { 29 int ta[N], cnt; 30 inline void add(int i) { 31 ++cnt; 32 // printf("Ans : add : %d ", i); 33 for(; i <= n; i += i & (-i)) { 34 ta[i]++; 35 } 36 return; 37 } 38 inline void del(int i) { 39 --cnt; 40 // printf("Ans : del : %d ", i); 41 for(; i <= n; i += i & (-i)) { 42 ta[i]--; 43 } 44 return; 45 } 46 inline int getMax() { 47 int ans = 0, k = cnt, t = pw[n]; 48 while(t >= 0) { 49 if(((ans | (1 << t)) <= n) && ta[ans | (1 << t)] < k) { 50 k -= ta[ans | (1 << t)]; 51 ans |= (1 << t); 52 } 53 t--; 54 } 55 return ans + 1; 56 } 57 } 58 59 namespace ta { 60 int ta[N], cnt; 61 inline void add(int i) { 62 ++cnt; 63 // printf("ta : add : %d ", i); 64 for(; i <= n; i += i & (-i)) { 65 ta[i]++; 66 } 67 return; 68 } 69 inline void del(int i) { 70 --cnt; 71 // printf("ta : del : %d ", i); 72 for(; i <= n; i += i & (-i)) { 73 ta[i]--; 74 } 75 return; 76 } 77 inline int getKth(int k) { 78 // printf("ta : Kth %d : ", k); 79 int ans = 0, t = pw[n]; 80 while(t >= 0) { 81 if(((ans | (1 << t)) <= n) && ta[ans | (1 << t)] < k) { 82 k -= ta[ans | (1 << t)]; 83 ans |= (1 << t); 84 } 85 t--; 86 } 87 // printf("%d cnt = %d ", ans + 1, cnt); 88 return ans + 1; 89 } 90 inline int getSum(int i) { 91 int ans = 0; 92 for(; i; i -= i & (-i)) { 93 ans += ta[i]; 94 } 95 return ans; 96 } 97 } 98 99 struct Ask { 100 int l, r, id; 101 inline bool operator <(const Ask &w) const { 102 if(fr[l] != fr[w.l]) return l < w.l; 103 return r < w.r; 104 } 105 }ask[N]; 106 107 inline void add(int x, int y) { 108 tp++; 109 edge[tp].v = y; 110 edge[tp].nex = e[x]; 111 e[x] = tp; 112 return; 113 } 114 115 void DFS_1(int x, int f) { 116 d[x] = d[f] + 1; 117 // printf("x = %d ", x); 118 pos[x] = ++num; 119 id[num] = x; 120 pos2[x] = ++num2; 121 ST[num2][0] = x; 122 for(int i = e[x]; i; i = edge[i].nex) { 123 int y = edge[i].v; 124 if(y == f) continue; 125 DFS_1(y, x); 126 ST[++num2][0] = x; 127 } 128 return; 129 } 130 131 inline void prework() { 132 register int i, j; 133 for(i = 2; i <= num2; i++) pw[i] = pw[i >> 1] + 1; 134 for(j = 1; j <= pw[num2]; j++) { 135 for(i = 1; i + (1 << j) - 1 <= num2; i++) { 136 if(d[ST[i][j - 1]] < d[ST[i + (1 << (j - 1))][j - 1]]) 137 ST[i][j] = ST[i][j - 1]; 138 else 139 ST[i][j] = ST[i + (1 << (j - 1))][j - 1]; 140 } 141 } 142 return; 143 } 144 145 inline int lca(int x, int y) { 146 x = pos2[x]; 147 y = pos2[y]; 148 if(x > y) std::swap(x, y); 149 int t = pw[y - x + 1]; 150 if(d[ST[x][t]] < d[ST[y - (1 << t) + 1][t]]) 151 return ST[x][t]; 152 else 153 return ST[y - (1 << t) + 1][t]; 154 } 155 156 inline void add(int x) { 157 // std::cerr << "------------ add " << x << std::endl; 158 ta::add(pos[x]); 159 // std::cerr << "111 "; 160 int rk = ta::getSum(pos[x]); 161 // std::cerr << "222 "; 162 int y = 0, z = 0; 163 if(rk != 1) { 164 y = id[ta::getKth(rk - 1)]; 165 } 166 if(rk != ta::cnt) { 167 z = id[ta::getKth(rk + 1)]; 168 } 169 // std::cerr << "333 "; 170 // out(y); out(z); 171 if(y) Ans::add(d[lca(x, y)]); 172 if(z) Ans::add(d[lca(x, z)]); 173 if(y && z) Ans::del(d[lca(y, z)]); 174 return; 175 } 176 177 inline void del(int x) { 178 // std::cerr << "------------ del " << x << std::endl; 179 int rk = ta::getSum(pos[x]); 180 int y = 0, z = 0; 181 if(rk != 1) { 182 y = id[ta::getKth(rk - 1)]; 183 } 184 if(rk != ta::cnt) { 185 z = id[ta::getKth(rk + 1)]; 186 } 187 if(y) Ans::del(d[lca(x, y)]); 188 if(z) Ans::del(d[lca(x, z)]); 189 if(y && z) Ans::add(d[lca(y, z)]); 190 ta::del(pos[x]); 191 return; 192 } 193 194 int main() { 195 196 freopen("lca.in", "r", stdin); 197 freopen("lca.out", "w", stdout); 198 199 register int i; 200 int m; 201 read(n); read(m); 202 for(int i = 1, x, y; i < n; i++) { 203 read(x); read(y); 204 add(x, y); add(y, x); 205 } 206 DFS_1(1, 0); 207 prework(); 208 int T = n / sqrt(m); 209 for(i = 1; i <= n; i++) { 210 fr[i] = (i - 1) / T + 1; 211 } 212 for(i = 1; i <= m; i++) { 213 read(ask[i].l); read(ask[i].r); 214 ask[i].id = i; 215 } 216 std::sort(ask + 1, ask + m + 1); 217 int l = 1, r = 1; ta::add(1); 218 for(i = 1; i <= m; i++) { 219 /*if(i % 1 == 0) { 220 std::cerr << "i = " << i << std::endl; 221 }*/ 222 // printf("i = %d [%d %d] ask [%d %d] ", i, l, r, ask[i].l, ask[i].r); 223 while(ask[i].l < l) { 224 add(--l); 225 } 226 while(r < ask[i].r) { 227 add(++r); 228 } 229 while(l < ask[i].l) { 230 del(l++); 231 } 232 while(ask[i].r < r) { 233 del(r--); 234 } 235 // printf("Ans = %d ", Ans::getMax()); 236 ans[ask[i].id] = Ans::getMax(); 237 } 238 for(i = 1; i <= m; i++) { 239 printf("%d ", ans[i]); 240 } 241 return 0; 242 }
②:
换反回滚莫队(只有删除),第一个数据结构换成链表。可以发现每次删掉一些节点然后按照原顺序插回来的话,可以做到O(1)前驱后继。然后第二个数据结构换成值域分块,可以O(1)修改√查询。
③:
这是一个nlog2n的做法。
考虑点x何时会被作为lca,显然就是在合并子树的时候,两个子树中各有一个点被选。
这个启发式一下,枚举小的那个子树,于是对于枚举到的每个点y,在另一棵子树中每个点被选都会导致x成为一次lca。
考虑到查询的编号总是连续的,于是只要在另一个子树中找到y的前驱后继即可。如果别的点和y有贡献,那么前驱和后继也一定有贡献。
于是我们有了O(nlogn)个点对和m个询问,全部按照左端点排序,从大到小枚举左端点,然后把点对加入。
开一个数据结构维护右端点恰为i的答案。于是答案就是一段前缀的最大值,树状数组即可。
1 #include <bits/stdc++.h> 2 3 using namespace std; 4 5 const int maxn = 80010; 6 int n,m,head[maxn],to[maxn * 2],nextt[maxn * 2],tot = 1,deep[maxn],cnt,ans[maxn],c[maxn]; 7 set<int> S[maxn]; 8 9 struct node 10 { 11 int x,y,v; 12 }e[2000010],q[2000010]; 13 14 inline int read() 15 { 16 int x = 0; 17 char ch = getchar(); 18 while (ch < '0' || ch > '9') 19 ch = getchar(); 20 while (ch >= '0' && ch <= '9') 21 { 22 x = (x << 3) + (x << 1) + ch - '0'; 23 ch = getchar(); 24 } 25 return x; 26 } 27 28 inline void add(int x,int y) 29 { 30 to[tot] = y; 31 nextt[tot] = head[x]; 32 head[x] = tot++; 33 } 34 35 inline void Merge(int x,int y) 36 { 37 if (S[x].size() < S[y].size()) 38 S[x].swap(S[y]); 39 for (set<int>::iterator it = S[y].begin(); it != S[y].end(); ++it) 40 { 41 int temp = (*it); 42 S[x].insert(temp); 43 set<int>::iterator it2 = S[x].find(temp); 44 if (it2 != S[x].begin()) 45 { 46 --it2; 47 ++cnt; 48 e[cnt].x = (*it2); 49 e[cnt].y = temp; 50 e[cnt].v = deep[x]; 51 } 52 it2 = S[x].find(temp); 53 ++it2; 54 if (it2 != S[x].end()) 55 { 56 ++cnt; 57 e[cnt].x = temp; 58 e[cnt].y = (*it2); 59 e[cnt].v = deep[x]; 60 } 61 } 62 S[y].clear(); 63 } 64 65 void dfs(int u,int faa) 66 { 67 deep[u] = deep[faa] + 1; 68 S[u].insert(u); 69 for (register int i = head[u];i;i = nextt[i]) 70 { 71 int v = to[i]; 72 if (v == faa) 73 continue; 74 dfs(v,u); 75 Merge(u,v); 76 } 77 } 78 79 inline void Add(int x,int v) 80 { 81 while (x <= n) 82 { 83 c[x] = max(c[x],v); 84 x += x & (-x); 85 } 86 } 87 88 inline int Query(int x) 89 { 90 int res = 0; 91 while (x) 92 { 93 res = max(res,c[x]); 94 x -= x & (-x); 95 } 96 return res; 97 } 98 99 inline bool cmp(node a,node b) 100 { 101 return a.x > b.x; 102 } 103 104 int main() 105 { 106 freopen("lca.in","r",stdin); 107 freopen("lca.out","w",stdout); 108 n = read(),m = read(); 109 for (register int i = 1; i < n; i++) 110 { 111 int x,y; 112 x = read(),y = read(); 113 add(x,y); 114 add(y,x); 115 } 116 dfs(1,0); 117 for (register int i = 1; i <= m; i++) 118 { 119 q[i].x = read(),q[i].y = read(); 120 q[i].v = i; 121 } 122 sort(q + 1,q + 1 + m,cmp); 123 sort(e + 1,e + 1 + cnt,cmp); 124 int cur = 1; 125 for (register int i = 1; i <= m; i++) 126 { 127 while (cur <= cnt && e[cur].x >= q[i].x) 128 { 129 Add(e[cur].y,e[cur].v); 130 cur++; 131 } 132 ans[q[i].v] = Query(q[i].y); 133 } 134 for (register int i = 1; i <= m; i++) 135 printf("%d ",ans[i]); 136 137 return 0; 138 }
可以用树套树做到在线。
④:
这是一个上界nlog²n的做法。参考资料。