求编号在区间[l, r]之间的两两lca的深度最大值。

例题

解:口胡几种做法。前两种基于莫队,第三种是启发式合并 + 扫描线,第四种是lct + 线段树。

①:

有个结论就是这个答案一定是点集中DFS序相邻的两个点的lca。于是开个数据结构,以DFS序为key维护点集,找前驱后继,额外用一个数据结构维护所有lca的深度,取最大值即可。外面套莫队就做完了。

实现上这两个数据结构都可以用树状数组。

 #include <bits/stdc++.h>

 #define out(a) std::cerr << #a" = " << a << std::endl;

 template <class T> inline void read(T &x) {
x = ;
char c = getchar();
while(c < '' || c > '') c = getchar();
while(c >= '' && c <= '') {
x = x * + c - ;
c = getchar();
}
return;
} const int N = ; struct Edge {
int nex, v;
}edge[N << ]; int tp; int e[N], pos[N], pos2[N], num, num2, fr[N], ans[N], id[N], ST[N << ][], d[N], pw[N << ], n;
/*std::set<int> st; /// save pos
std::set<int>::iterator it;*/
/*std::multiset<int> Ans;
std::multiset<int>::iterator it2;*/ namespace Ans {
int ta[N], cnt;
inline void add(int i) {
++cnt;
// printf("Ans : add : %d \n", i);
for(; i <= n; i += i & (-i)) {
ta[i]++;
}
return;
}
inline void del(int i) {
--cnt;
// printf("Ans : del : %d \n", i);
for(; i <= n; i += i & (-i)) {
ta[i]--;
}
return;
}
inline int getMax() {
int ans = , k = cnt, t = pw[n];
while(t >= ) {
if(((ans | ( << t)) <= n) && ta[ans | ( << t)] < k) {
k -= ta[ans | ( << t)];
ans |= ( << t);
}
t--;
}
return ans + ;
}
} namespace ta {
int ta[N], cnt;
inline void add(int i) {
++cnt;
// printf("ta : add : %d \n", i);
for(; i <= n; i += i & (-i)) {
ta[i]++;
}
return;
}
inline void del(int i) {
--cnt;
// printf("ta : del : %d \n", i);
for(; i <= n; i += i & (-i)) {
ta[i]--;
}
return;
}
inline int getKth(int k) {
// printf("ta : Kth %d : ", k);
int ans = , t = pw[n];
while(t >= ) {
if(((ans | ( << t)) <= n) && ta[ans | ( << t)] < k) {
k -= ta[ans | ( << t)];
ans |= ( << t);
}
t--;
}
// printf("%d cnt = %d \n", ans + 1, cnt);
return ans + ;
}
inline int getSum(int i) {
int ans = ;
for(; i; i -= i & (-i)) {
ans += ta[i];
}
return ans;
}
} struct Ask {
int l, r, id;
inline bool operator <(const Ask &w) const {
if(fr[l] != fr[w.l]) return l < w.l;
return r < w.r;
}
}ask[N]; inline void add(int x, int y) {
tp++;
edge[tp].v = y;
edge[tp].nex = e[x];
e[x] = tp;
return;
} void DFS_1(int x, int f) {
d[x] = d[f] + ;
// printf("x = %d \n", x);
pos[x] = ++num;
id[num] = x;
pos2[x] = ++num2;
ST[num2][] = x;
for(int i = e[x]; i; i = edge[i].nex) {
int y = edge[i].v;
if(y == f) continue;
DFS_1(y, x);
ST[++num2][] = x;
}
return;
} inline void prework() {
register int i, j;
for(i = ; i <= num2; i++) pw[i] = pw[i >> ] + ;
for(j = ; j <= pw[num2]; j++) {
for(i = ; i + ( << j) - <= num2; i++) {
if(d[ST[i][j - ]] < d[ST[i + ( << (j - ))][j - ]])
ST[i][j] = ST[i][j - ];
else
ST[i][j] = ST[i + ( << (j - ))][j - ];
}
}
return;
} inline int lca(int x, int y) {
x = pos2[x];
y = pos2[y];
if(x > y) std::swap(x, y);
int t = pw[y - x + ];
if(d[ST[x][t]] < d[ST[y - ( << t) + ][t]])
return ST[x][t];
else
return ST[y - ( << t) + ][t];
} inline void add(int x) {
// std::cerr << "------------ add " << x << std::endl;
ta::add(pos[x]);
// std::cerr << "111 \n";
int rk = ta::getSum(pos[x]);
// std::cerr << "222 \n";
int y = , z = ;
if(rk != ) {
y = id[ta::getKth(rk - )];
}
if(rk != ta::cnt) {
z = id[ta::getKth(rk + )];
}
// std::cerr << "333 \n";
// out(y); out(z);
if(y) Ans::add(d[lca(x, y)]);
if(z) Ans::add(d[lca(x, z)]);
if(y && z) Ans::del(d[lca(y, z)]);
return;
} inline void del(int x) {
// std::cerr << "------------ del " << x << std::endl;
int rk = ta::getSum(pos[x]);
int y = , z = ;
if(rk != ) {
y = id[ta::getKth(rk - )];
}
if(rk != ta::cnt) {
z = id[ta::getKth(rk + )];
}
if(y) Ans::del(d[lca(x, y)]);
if(z) Ans::del(d[lca(x, z)]);
if(y && z) Ans::add(d[lca(y, z)]);
ta::del(pos[x]);
return;
} int main() { freopen("lca.in", "r", stdin);
freopen("lca.out", "w", stdout); register int i;
int m;
read(n); read(m);
for(int i = , x, y; i < n; i++) {
read(x); read(y);
add(x, y); add(y, x);
}
DFS_1(, );
prework();
int T = n / sqrt(m);
for(i = ; i <= n; i++) {
fr[i] = (i - ) / T + ;
}
for(i = ; i <= m; i++) {
read(ask[i].l); read(ask[i].r);
ask[i].id = i;
}
std::sort(ask + , ask + m + );
int l = , r = ; ta::add();
for(i = ; i <= m; i++) {
/*if(i % 1 == 0) {
std::cerr << "i = " << i << std::endl;
}*/
// printf("i = %d [%d %d] ask [%d %d] \n", i, l, r, ask[i].l, ask[i].r);
while(ask[i].l < l) {
add(--l);
}
while(r < ask[i].r) {
add(++r);
}
while(l < ask[i].l) {
del(l++);
}
while(ask[i].r < r) {
del(r--);
}
// printf("Ans = %d \n", Ans::getMax());
ans[ask[i].id] = Ans::getMax();
}
for(i = ; i <= m; i++) {
printf("%d\n", ans[i]);
}
return ;
}

代码

②:

换反回滚莫队(只有删除),第一个数据结构换成链表。可以发现每次删掉一些节点然后按照原顺序插回来的话,可以做到O(1)前驱后继。然后第二个数据结构换成值域分块,可以O(1)修改√查询。

③:

这是一个nlog2n的做法。

考虑点x何时会被作为lca,显然就是在合并子树的时候,两个子树中各有一个点被选。

这个启发式一下,枚举小的那个子树,于是对于枚举到的每个点y,在另一棵子树中每个点被选都会导致x成为一次lca。

考虑到查询的编号总是连续的,于是只要在另一个子树中找到y的前驱后继即可。如果别的点和y有贡献,那么前驱和后继也一定有贡献。

于是我们有了O(nlogn)个点对和m个询问,全部按照左端点排序,从大到小枚举左端点,然后把点对加入。

开一个数据结构维护右端点恰为i的答案。于是答案就是一段前缀的最大值,树状数组即可。

 #include <bits/stdc++.h>

 using namespace std;

 const int maxn = ;
int n,m,head[maxn],to[maxn * ],nextt[maxn * ],tot = ,deep[maxn],cnt,ans[maxn],c[maxn];
set<int> S[maxn]; struct node
{
int x,y,v;
}e[],q[]; inline int read()
{
int x = ;
char ch = getchar();
while (ch < '' || ch > '')
ch = getchar();
while (ch >= '' && ch <= '')
{
x = (x << ) + (x << ) + ch - '';
ch = getchar();
}
return x;
} inline void add(int x,int y)
{
to[tot] = y;
nextt[tot] = head[x];
head[x] = tot++;
} inline void Merge(int x,int y)
{
if (S[x].size() < S[y].size())
S[x].swap(S[y]);
for (set<int>::iterator it = S[y].begin(); it != S[y].end(); ++it)
{
int temp = (*it);
S[x].insert(temp);
set<int>::iterator it2 = S[x].find(temp);
if (it2 != S[x].begin())
{
--it2;
++cnt;
e[cnt].x = (*it2);
e[cnt].y = temp;
e[cnt].v = deep[x];
}
it2 = S[x].find(temp);
++it2;
if (it2 != S[x].end())
{
++cnt;
e[cnt].x = temp;
e[cnt].y = (*it2);
e[cnt].v = deep[x];
}
}
S[y].clear();
} void dfs(int u,int faa)
{
deep[u] = deep[faa] + ;
S[u].insert(u);
for (register int i = head[u];i;i = nextt[i])
{
int v = to[i];
if (v == faa)
continue;
dfs(v,u);
Merge(u,v);
}
} inline void Add(int x,int v)
{
while (x <= n)
{
c[x] = max(c[x],v);
x += x & (-x);
}
} inline int Query(int x)
{
int res = ;
while (x)
{
res = max(res,c[x]);
x -= x & (-x);
}
return res;
} inline bool cmp(node a,node b)
{
return a.x > b.x;
} int main()
{
freopen("lca.in","r",stdin);
freopen("lca.out","w",stdout);
n = read(),m = read();
for (register int i = ; i < n; i++)
{
int x,y;
x = read(),y = read();
add(x,y);
add(y,x);
}
dfs(,);
for (register int i = ; i <= m; i++)
{
q[i].x = read(),q[i].y = read();
q[i].v = i;
}
sort(q + ,q + + m,cmp);
sort(e + ,e + + cnt,cmp);
int cur = ;
for (register int i = ; i <= m; i++)
{
while (cur <= cnt && e[cur].x >= q[i].x)
{
Add(e[cur].y,e[cur].v);
cur++;
}
ans[q[i].v] = Query(q[i].y);
}
for (register int i = ; i <= m; i++)
printf("%d\n",ans[i]); return ;
}

代码

可以用树套树做到在线。

④:

这是一个上界nlog²n的做法。参考资料

区间最深LCA的更多相关文章

  1. 区间节点的lca

    题目hdu5266 分析:多节点的LCA就是dfs序中最大最小两个节点的LCA.所以只要每次维持给出节点的dfs序的最大最小,然后就是两点的LCA 代码: rmq的st+lca的倍增 #include ...

  2. HDU 6065 RXD, tree and sequence (LCA DP)

    RXD, tree and sequence Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 524288/524288 K (Java ...

  3. 2016-2017 ACM-ICPC, NEERC, Northern Subregional Contest

    A. Anniversary Cake 随便挑两个点切掉就好了. #include<bits/stdc++.h> using namespace std; const int Maxn=2 ...

  4. 【BZOJ】3757: 苹果树

    http://www.lydsy.com/JudgeOnline/problem.php?id=3757 题意:n个节点的树,每个点有一种颜色.现有m种询问,每次询问x y a b表示x到y的路径上颜 ...

  5. Codeforces 1062E 题解

    给出一棵有根树,1为根结点,接下来q次询问,每次给出一个[l,r]区间,现在允许删掉[l,r]区间内任何一个点,使得所有点的最近公共祖先的深度尽可能大,问删掉的点是哪个点,深度最大是多少. 做法: 线 ...

  6. CSPS模拟94

    我好菜啊...... %%%迪神AK 虽然考试成绩不太好,但至少能想到正解了,也不会菜到打不出暴力. T1:想了半天不会,发现直接打高精可以拿到80分,就赶紧码完扔了,结果正解是利用double避免了 ...

  7. HDU 5266 pog loves szh III(区间LCA)

    题目链接 pog loves szh III 题意就是  求一个区间所有点的$LCA$. 我们把$1$到$n$的$DFS$序全部求出来……然后设$i$的$DFS$序为$c[i]$,$pc[i]$为$c ...

  8. hdu 5044 树区间操作最后输出/ lca+dfs

    题意:一棵树,俩种操作:1 有路径上的全部点加vi,2全部边加vi. 先离线求出全部询问的lca,再遍历询问一次,点+vi,lca-2*vi ,最后dfs从叶子扫上来一次,最后再祖先点补上就可以.用了 ...

  9. [hdu5266]区间LCA

    题意:给一棵树,求节点L,L+1,...R的最近公共祖先 思路:先对树dfs一下,从根1出发,经过每条边时记录一下终点和到达这个点的时间截,令r[u]表示到达u这个节点的最早时间截,t[x]表示在时间 ...

随机推荐

  1. 前端开发之css

    <!--页面中的组成部分通常随便打开一个网页,有文字,图片,视频,表格,音频,表单(注册信息) css 属性/尺寸/边框/背景 1.css的尺寸属性,就是大小width max-width mi ...

  2. 《笔记》Apache2 mod_wsgi的配置

    接手了一台古老的服务器的还使用的是mod_wsgi,所以需要配置一下.其实这里有点怀念,记得当年自己折腾第一个app的时候,还是个什么都不懂的菜鸡.当时用django搜方案的时候,还不知道有uwsgi ...

  3. 一、PHP_OSS使用

    一.OSS PHP SDK下载 二.文件目录 三.参考手册快速入门对oss操作 以及到控制台找到相应参数并填写

  4. How to ssh

    ssh -p 22 cuthead@127.0.0.1

  5. Detected problems with API compatibility(visit g.co/dev/appcompat for more info)

    应用开启了debug模式导致Android 9提示如此,使用release模式即可解决.

  6. maven 当两个工程合并后 他的classpath也合并了

    maven   当两个工程合并后 他的classpath也合并了  也就是说资源文件环境合并了

  7. C 语言----- 指针

    指针是一个值为内存地址的变量, 指针的核心是它是一个变量, 只不过它是用来存放内存地址的, 所以在了解指针之前,先说一下什么是变量.变量就是在内存中开辟的一个空间.如int year, 就是在内存中开 ...

  8. codeforces263B

    Squares CodeForces - 263B Vasya has found a piece of paper with a coordinate system written on it. T ...

  9. Nginx http2.0

    109/110 HTTP2.0协议 优势必须使用TLS加密 传输数据量大幅减少 1:以二进制格式传输  2:标头压缩(header做压缩) 多路复用及相关功能 : 消息优先级 (比如样式表先渲染页面那 ...

  10. .net core 2.0 数据访问-迁移

    将用于进行迁移的 Entity Framework Core NuGet包 添加到`.csproj`文件 <ItemGroup> <DotNetCliToolReference In ...