区间最深LCA
求编号在区间[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的更多相关文章
- 区间节点的lca
题目hdu5266 分析:多节点的LCA就是dfs序中最大最小两个节点的LCA.所以只要每次维持给出节点的dfs序的最大最小,然后就是两点的LCA 代码: rmq的st+lca的倍增 #include ...
- 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 ...
- 2016-2017 ACM-ICPC, NEERC, Northern Subregional Contest
A. Anniversary Cake 随便挑两个点切掉就好了. #include<bits/stdc++.h> using namespace std; const int Maxn=2 ...
- 【BZOJ】3757: 苹果树
http://www.lydsy.com/JudgeOnline/problem.php?id=3757 题意:n个节点的树,每个点有一种颜色.现有m种询问,每次询问x y a b表示x到y的路径上颜 ...
- Codeforces 1062E 题解
给出一棵有根树,1为根结点,接下来q次询问,每次给出一个[l,r]区间,现在允许删掉[l,r]区间内任何一个点,使得所有点的最近公共祖先的深度尽可能大,问删掉的点是哪个点,深度最大是多少. 做法: 线 ...
- CSPS模拟94
我好菜啊...... %%%迪神AK 虽然考试成绩不太好,但至少能想到正解了,也不会菜到打不出暴力. T1:想了半天不会,发现直接打高精可以拿到80分,就赶紧码完扔了,结果正解是利用double避免了 ...
- HDU 5266 pog loves szh III(区间LCA)
题目链接 pog loves szh III 题意就是 求一个区间所有点的$LCA$. 我们把$1$到$n$的$DFS$序全部求出来……然后设$i$的$DFS$序为$c[i]$,$pc[i]$为$c ...
- hdu 5044 树区间操作最后输出/ lca+dfs
题意:一棵树,俩种操作:1 有路径上的全部点加vi,2全部边加vi. 先离线求出全部询问的lca,再遍历询问一次,点+vi,lca-2*vi ,最后dfs从叶子扫上来一次,最后再祖先点补上就可以.用了 ...
- [hdu5266]区间LCA
题意:给一棵树,求节点L,L+1,...R的最近公共祖先 思路:先对树dfs一下,从根1出发,经过每条边时记录一下终点和到达这个点的时间截,令r[u]表示到达u这个节点的最早时间截,t[x]表示在时间 ...
随机推荐
- 在Laravel中使用数据库事务以及捕获事务失败后的异常
Description 在Laravel中要想在数据库事务中运行一组操作,则可以在 DB facade 中使用 transaction 方法.如果在事务的闭包内抛出异常,事务将会被自动还原.如果闭包运 ...
- 牛客练习赛13D 幸运数字4
题目链接:https://ac.nowcoder.com/acm/contest/70/D 题目大意: 略 分析: 注意到12! < 10^9 < 13!,于是当n > 13时,第k ...
- WPF中元素拖拽的两个实例
今天结合之前做过的一些拖拽的例子来对这个方面进行一些总结,这里主要用两个例子来说明在WPF中如何使用拖拽进行操作,元素拖拽是一个常见的操作,第一个拖拽的例子是将ListBox中的子元素拖拽到ListV ...
- Centos rpm包安装PHP所需包
yum -y install php php-devel php-fpm php-xml php-pdo php-ldap php-mysql
- MyBatis基础:MyBatis动态SQL(3)
1. 概述 MyBatis中动态SQL包括元素: 元素 作用 备注 if 判断语句 单条件分支判断 choose(when.otherwise) 相当于Java中的case when语句 多条件分支判 ...
- Lodop打印旋转180度 倒着打
方法1:打印出来后,直接把纸张倒过来.如果本身是白纸,打印机出纸内容是倒着的,可以打出来后手动倒着把纸张正过来.如果本身不是白纸,需要打印的纸张上有背景,调整进纸方向.(如果是卷纸,卷纸背景是反的,查 ...
- Maven依赖范围及传递
.Maven因为执行一系列编译.测试和部署运行等操作,在不同的操作下使用的classpath不同,依赖范围就是用来控制依赖与三种 classpath(编译classpath.测试classpath.运 ...
- 配置Web.config 元素CustomErrors
一.customErrors 元素 属性 说明 defaultRedirect 指定出错时将浏览器定向到的默认 URL.如果未指定该属性,则显示一般性错误. 可选的属性. URL 可以是绝对的(如 w ...
- Ubuntu下安装tomcat
下面记录了Ubuntu 16.04下安装Tomcat 8.5.9的过程步骤. 1.到官网下载tomcat8.5.9,选择格式为tar.gz.2.通过ftp将下载的tomcat8.5.9压缩包上传到ub ...
- Nginx 简单的cpu配置
配置指定CPU Nginx建议进程数和CPU数量一致,这样每个CPU都有自己独立的缓存 worker_processes 4; worker_cpu_affinity 1000 0100 0010 0 ...