「NOIP2024」 树上查询

news/2025/1/18 17:38:30/文章来源:https://www.cnblogs.com/keysky/p/18678644

update 2024/12/28

题目描述

给定一棵树,每次询问区间 \([l,r]\)

\[\max_{l \le l' \le r' \le r \land r' - l' + 1 \ge k}\text{dep}_ {\text{LCA*}(l', r')} \]

引理证明

先来证两个区间 \(\text{LCA}\) 的引理:
对于 \(\text{LCA} \{ l, l + 1, \dots r\}\) 我们有 \(\text{LCA} \{ l, l + 1, \dots r\}\)\([l, r]\)\(\text{dfs}\) 序最小的点和最大的点的 \(\text{LCA}\) 。证明:
假设点 \(u\) 是区间 \([l, r]\)\(\text{dfs}\) 序最小的点 \(i\) 和最大的点 \(j\)\(\text{LCA}\) ,则 \(dfn_u \leq dfn_i \leq dfn_j \leq out_u\)。于是我们对于区间 \([l, r]\) 中任意一个点 \(v\)\(dep_i \leq dep_v \leq dep_j\),则 \(dep_u \leq dep_v \leq out_u\) ,所以区间中所有点都在 \(u\) 的子树中。


此外我们有 \(\text{LCA} \{ l, l + 1, \dots , r \}\) 为所有 \(\text{LCA(i, i + 1)} (l \leq i < r)\) 中最靠近根节点的那个。证明:
\(c_i = dep_{\text{LCA}(i, i + 1)}\) ,则对于结点 \(u\)\(v\)\(dep_u \leq dep_v\) 可以说点 \(u\) 比点 \(v\) 更靠近根节点。设点 \(u\) 是区间 \([l, r]\)\(\text{LCA}\) ,则一定存在两个点 \(x\)\(y\) 来自 \(u\) 的不同子树,此时 \([x, y)\) 中一定存在一个 \(v\) 使得 \(v\)\(v + 1\) 来自不同子树,即 \(c_v = dep_u\) ,此时 \(\text{LCA} (v, v + 1) = u\)

思路推导

首先,对于区间 \(\text{LCA}\) ,我们已知有两个求法了,在此题中,如果要找 \(\text{dfs}\) 序最小最大需要额外维护一次最大最小,是没有前途的,所以针对第二种方法进行优化。
\(i\)\(i + 1\) 两点的 \(dep_{\text{LCA}}\) 作为 \(c_i\) ,问题就转化为对于每个区间求

\[\max_{l \le l' \le r' \le r \land r' - l' + 1 \ge k}{\max_{l' \leq i < r'}{c_i}} \]

对于 \(k = 1\) 的情况额外用 \(\text{RMQ}\) 处理。
首先我们很容易想到的是对于区间长度大于 \(k\) 的一定不优于区间长度等于 \(k\) 的,这一点很好证明,于是式子就变成

\[\max_{l \le l' \le r' \le r \land r' - l' + 1 = k}{\max_{l' \leq i < r'}{c_i}} \]

一个相对来说比较容易想到的方法是计算每个点作为 \(\text{LCA}\) 的贡献。对于任意一个 \(i\) ,我们定义 \(pre_i\) 表示在 \(i\) 左边第一个 \(j + 1\) 使得 \(c_j \leq c_i\)\(suf_i\) 表示在 \(i\) 右边第一个 \(j - 1\) 使得 \(c_j < c_i\)这一定是左边取 \(\leq\) 右边取 \(<\) 或左边取 \(<\) 右边取 \(\leq\),原因后文再说),这可以通过单调栈求取。对于 \(i\) ,能产生贡献的区间为 \(l=[pre_i + 1 , i]\)\(r=[i , suf_i - 1]\) ,容易想到转化为二位数点问题,该贡献区间可转化为以下矩形:
贡献矩形
对于查询,也就转换为了一条斜率为 \(1\) 的线段,如下图:
查询线段
显然对于 \(3\) 个方向的扫描线我们是没法维护的(至少我不知道),所以我们要将其拆分。
对于查询的线段,我们把它分为两个情况:

  • 全部被包含于一个矩形
  • 跨越多个矩形

对于第 \(1\) 种情况,扫描线板子解决。
对于第 \(2\) 种情况,我们可以发现如果该线段跨越矩阵的话一定会与矩形的边界有交点,于是我们可以只保留矩形的 \(4\) 条边,再拆分为横向和竖向的 \(2\) 对边,加上查询的斜线,就转换为两遍分别有两个方向的扫描线,如下图:
横向边贡献 \(+\) 查询
扫描线1-1
竖向边贡献 \(+\) 查询
扫描线2-1
但我们看到对于查询线段是斜着的,是不能用扫描线查询的,但整张图中是只有两个方向的,所以可以想到通过旋转坐标轴的 \(\hat{i}\)\(\hat{j}\) 来拉平查询线段使得所有线“横平竖直”。
对于第 \(2\) 张图拉直过后像这样:
扫描线2-2
对于第一张图留给读者思考(才不是因为我不想画了)
最后还有一个问题:扫描线取最大值的影响怎么消除,这就关系到前文提到关于区间边界的问题了,正常情况下所有贡献矩形是不会产生交集的(原因可以自己手推一下一对 \(i,j\) 使得 \(i \leq j\)\(c_i < c_j\)\(c_j < c_i\)的两种情况),除非出现两个 \(c_i\) 相等,此时如前文所述改为类似左闭右开的方法便可令其也不产生交集同时不重不漏。此时维护扫描线即可区间覆盖,区间取 \(\max\) 了。
所以统共来说是 \(3\) 遍扫描线 \(+\) \(1\)\(\text{RMQ}\),还是比较恶心的(当然是我的思路和做法有些复杂了,其它还有些相对更简便的做法)。

解法概括

对于点 \(i\) 定义 \(c_i\)\(dep_{\text{LCA}(i,i + 1)}\) ,转换为序列问题。
\(k \geq 2\) 则对于 \(c_i\) 产生的贡献矩形将其拆分为两对相互平行的线段,旋转坐标轴,让查询斜线变为横线或竖线,进行 \(2\) 遍扫描线,再与线段一端点值取 \(\max\) ,对于 \(k = 1\) 的情况跑一遍 \(\text{RMQ}\) ,最后统计答案

实现

注意一些关于下标、边界的实现细节。
我之前是将线段树的功能全用区修区查来实现的,同时扫描线无论特殊性全写在 solve 中,第一次过了,没注意 \(1.98s\) 的时间,后来再交就一直 \(\text{TLE}\) ,于是修改了一些不必要的懒标记和区修,哪怕删了注释的调试,代码还是多了三十几行,但能保证不 \(\text{TLE}\) 了。
对于我这个思路,理论时间复杂度为 \(O(N log N)\) ,但常数巨大,按最坏情况分析:扫描线 \(4\) 倍,线段树 \(4\) 倍,\(3\) 遍扫描线,总计 \(\times 48\) ,但实际只有大约一半的常数。

#include<bits/stdc++.h>
using namespace std;
const int N = 5e5 + 5;
int n, q;
vector<int>G[N];
struct line {int x, y, k;bool operator < (const line& o)const { return x == o.x ? k < o.k : x < o.x; }
}li[N << 2];
struct node {int x, dy, ty, k;bool operator < (const node& o)const { return x == o.x ? k < o.k : x < o.x; }
}li2[N << 2];
struct query {int x, l, r, id;bool operator < (const query& o)const { return x < o.x; }
}qry[N];
int ans[N];
struct SegmentTree {
#define ls (id << 1)
#define rs (id << 1 | 1)
#define mid (l + r >> 1)struct Segment {int mx;}seg[N << 2];inline void merge(int id) { seg[id].mx = max(seg[ls].mx, seg[rs].mx); }inline void build(int id, int l, int r) {seg[id].mx = 1;if (l == r) return;build(ls, l, mid);build(rs, mid + 1, r);}inline void pushdown(int id) {if (seg[id].mx != -1) {seg[ls].mx = seg[id].mx;seg[rs].mx = seg[id].mx;seg[id].mx = -1;}}inline void change(int id, int l, int r, int x, int k) {if (l == r) {seg[id].mx = k;return;}if (x <= mid) change(ls, l, mid, x, k);else change(rs, mid + 1, r, x, k);merge(id);}inline int query(int id, int l, int r, int L, int R) {if (l >= L && R >= r) return seg[id].mx;int ret = 1;if (L <= mid) ret = max(ret, query(ls, l, mid, L, R));if (R > mid) ret = max(ret, query(rs, mid + 1, r, L, R));return ret;}inline void modify(int id, int l, int r, int L, int R, int k) {if (l >= L && R >= r) {seg[id].mx = k;return;}pushdown(id);if (L <= mid) modify(ls, l, mid, L, R, k);if (R > mid) modify(rs, mid + 1, r, L, R, k);}inline int get(int id, int l, int r, int x) {if (l == r) return seg[id].mx;pushdown(id);if (x <= mid) return get(ls, l, mid, x);else return get(rs, mid + 1, r, x);}
}SGT;
const int K = 21;
int up[K + 1][N], dep[N];
inline void dfs(int u, int fa) {for (int i = 1;i < K;i++)up[i][u] = up[i - 1][up[i - 1][u]];for (auto v : G[u]) {if (v == fa) continue;up[0][v] = u;dep[v] = dep[u] + 1;dfs(v, u);}
}
inline void lift(int& x, int k) {for (int i = K - 1;i >= 0;i--)if (k >> i & 1) x = up[i][x];
}
inline int lca(int a, int b) {lift(a, dep[a] - min(dep[a], dep[b]));lift(b, dep[b] - min(dep[a], dep[b]));if (a == b) return a;for (int i = K - 1;i >= 0;i--)if (up[i][a] != up[i][b]) a = up[i][a], b = up[i][b];return up[0][a];
}
int l0[N], r0[N], k0[N];
int pre[N], nxt[N], c[N];
int stk[N], top;
int qc, m;
inline void solve() {sort(qry + 1, qry + qc + 1);sort(li + 1, li + m + 1);SGT.build(1, 1, n);int j = 1;for (int i = 1;i <= qc;i++) {while (li[j].x <= qry[i].x && j <= m) {SGT.change(1, 1, n, li[j].y, li[j].k);j++;}ans[qry[i].id] = max(ans[qry[i].id], SGT.query(1, 1, n, qry[i].l, qry[i].r));}
}
int ST[K + 1][N], LG[N];
inline void init() {for (int i = 2;i <= n;i++) LG[i] = LG[i >> 1] + 1;for (int i = 1;i <= n;i++) ST[0][i] = dep[i];for (int i = 1;i < K;i++)for (int j = 1;j + (1 << i) - 1 <= n;j++)ST[i][j] = max(ST[i - 1][j], ST[i - 1][j + (1 << i - 1)]);
}
inline int RMQ(int l, int r) {int i = LG[r - l + 1];return max(ST[i][l], ST[i][r - (1 << i) + 1]);
}
int main() {// freopen("query.in", "r", stdin);// freopen("query.out", "w", stdout);scanf("%d", &n);for (int i = 1;i < n;i++) {int u, v;scanf("%d%d", &u, &v);G[u].push_back(v);G[v].push_back(u);}scanf("%d", &q);for (int i = 1;i <= q;i++) scanf("%d%d%d", &l0[i], &r0[i], &k0[i]);dep[1] = 1;dfs(1, 0);for (int i = 1;i < n;i++) c[i] = dep[lca(i, i + 1)];// for (int i = 1;i < n;i++) printf("%d ", c[i]);// puts("");stk[++top] = 0;for (int i = 1;i < n;i++) {while (c[stk[top]] >= c[i] && top > 0) top--;pre[i] = stk[top] + 1;stk[++top] = i;}top = 0;stk[++top] = n;for (int i = n - 1;i >= 1;i--) {while (c[stk[top]] > c[i] && top > 0) top--;nxt[i] = stk[top] - 1;stk[++top] = i;}// for (int i = 1;i < n;i++) printf("%d ", pre[i]);// puts("");// for (int i = 1;i < n;i++) printf("%d ", nxt[i]);// puts("");/*matrix: (pre[i], nxt[i]) -------- (i, nxt[i])(pre[i], i) ------------------(i, i)line:(r0[i] - k0[i] + 1, r0[i] - 1)/////////(l0[i], l0[i] + k0[i] - 2)*/m = qc = 0;for (int i = 1;i < n;i++) {li[++m] = { pre[i] - nxt[i],nxt[i],c[i] };li[++m] = { i + 1 - nxt[i],nxt[i], 1 };li[++m] = { pre[i] - i,i,c[i] };li[++m] = { i + 1 - i,i,1 };}for (int i = 1;i <= q;i++)if (k0[i] > 1)qry[++qc] = { l0[i] - (l0[i] + k0[i] - 2),l0[i] + k0[i] - 2,r0[i] - 1,i };solve();/*matrix: (pre[i], nxt[i])          (i, nxt[i])|                           ||                           ||                           ||                           ||                           ||                           |(pre[i], i)                  (i, i)line:(r0[i] - k0[i] + 1, r0[i] - 1)/////////(l0[i], l0[i] + k0[i] - 2)*/m = qc = 0;for (int i = 1;i < n;i++) {li[++m] = { i - pre[i],pre[i],c[i] };li[++m] = { nxt[i] + 1 - pre[i],pre[i],1 };li[++m] = { i - i,i,c[i] };li[++m] = { nxt[i] + 1 - i,i,1 };}for (int i = 1;i <= q;i++)if (k0[i] > 1)qry[++qc] = { l0[i] + k0[i] - 2 - l0[i],l0[i],r0[i] - k0[i] + 1,i };solve();/*matrix: (pre[i], nxt[i]) -------- (i, nxt[i])|                           ||                           ||                           ||                           ||                           ||                           |(pre[i], i) ------------------(i, i)line:.(l0[i], l0[i] + k0[i] - 2)*/m = qc = 0;for (int i = 1;i < n;i++) {li2[++m] = { pre[i],i,nxt[i],c[i] };li2[++m] = { i + 1,i,nxt[i],1 };}for (int i = 1;i <= q;i++)if (k0[i] > 1)qry[++qc] = { l0[i],l0[i] + k0[i] - 2,l0[i] + k0[i] - 2,i };// for (int i = 1;i <= m;i++) printf("%d %d %d %d\n", li[i].x, li[i].dy, li[i].ty, li[i].k);sort(qry + 1, qry + qc + 1);sort(li2 + 1, li2 + m + 1);SGT.build(1, 1, n);int j = 1;for (int i = 1;i <= qc;i++) {while (li2[j].x <= qry[i].x && j <= m) {SGT.modify(1, 1, n, li2[j].dy, li2[j].ty, li2[j].k);j++;}ans[qry[i].id] = max(ans[qry[i].id], SGT.get(1, 1, n, qry[i].l));}init();for (int i = 1;i <= q;i++)if (k0[i] == 1) ans[i] = max(ans[i], RMQ(l0[i], r0[i]));for (int i = 1;i <= q;i++) printf("%d\n", ans[i]);return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/871321.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Flask Web开发实战:入门、进阶与原理解析PDF免费下载

PythonWeb框架Flask开发团队成员撰写,内容全面,从基础知识到进阶实战,再到源码分析,提供完善的Flask学习路径适读人群 :本书适合了解Python基本语法,想要自己动手做网站的编程人员;熟悉Python。想要从事Python Web开发的后端工程师、运维工程师和爬虫工程师;香葱Django…

CMU 15-445 23Fall总结

注:编译、测试之前运行sudo sysctl vm.mmap_rnd_bits=28 BusTubs architecture: 1. Query Processing (查询处理层) 负责将输入的 SQL 查询转化为可执行的物理查询计划。Parser(解析器):将输入的 SQL 字符串解析为抽象语法树 (AST),检查 SQL 语法是否合法。 Binder(绑定器…

从数据到模型,足球预测方法解析

在足球赛事范畴内,比赛结局始终蕴含着诸多不确定性,而这恰恰构成了足球独特的魅力要素。对于广大球迷而言,尝试预测足球比赛的最终结果,向来是一项极具吸引力与挑战性的活动。 近年来,伴随数据科学以及机器学习技术的迅猛发展,足球预测领域发生了深刻变革。这些先进技术为…

传奇三虚拟机服务端-客户端win10可用

论坛转来的,还没有实验架设 传奇3 虚拟机服务端一键架设。。。好吧,三键架设,据说WIN10可玩服务端启动稍微有点步骤,还算简单吧QQ截图20200414142743.jpg (73.53 KB, 下载次数: 0)下载附件2020-4-14 14:41 上传QQ截图20200414142828.jpg (74.73 KB, 下载次数: 0)下载附件20…

THREE.js学习笔记9——Materials

这一小节主要学习材质 材质用于为几何物理模型的每个可见像素添加颜色。 Materials are used to put a color on each visible pixel of the geometries. 决定每个像素颜色的算法是在程序中编写的,称为着色器。 Three.js 具有许多带有预制着色器的内置材料。 Algorithms that …

[HarekazeCTF2019]baby_rop2(read的libc)

一个normal的栈溢出,没有system和binsh,为ret2libc 这里也没有常见的write和puts,所以我们用read泄露libc基址,并使用printf打印read的地址 这里注意printf的第一个参数必须是格式字符串,即Welcome to the Pwn World again(地址为0x0400770,第二个参数设为read_got(got表…

Living-Dream 系列笔记 第93期

最大流 EK & Dinic本文讲解 EK & Dinic 算法。 最大流 最大流的模型:特别注意:这个流量上限不是单次流量不超过它,而是多次的总和不超过它。 EK 显然这个问题是可以使用 dfs 解决的,但是效率低下。 考虑如下的图。我们发现 dfs 有可能走了 \(S \to A \to B \to T\)…

【每日一题】20250118

我是时间唯一的主人。成为自己的时间的主人是一种奢侈。我认为这是人类能够送给自己的最奢侈的东西之一。【每日一题】 1.(16分) \(\hspace{0.6cm}\)如图所示,在以坐标原点 \(O\) 为圆心、半径为 \(R\) 的半圆形区域内,有相互垂直的匀强电场和匀强磁场,磁感应强度为 \(B\),…

思通数科舆情监测系统:精准实现数据监测与实时预警的应用意义

随着信息化社会的深入发展,舆情管理变得愈加复杂,尤其是在社交媒体和网络平台的广泛应用下,信息传播的速度与影响力呈现出指数级增长。如何高效监测和分析这些海量数据,成为各级政府、企业和公共机构亟待解决的问题。思通数科的舆情监测系统,凭借强大的数据监控与分析能力…

中考英语优秀范文-热点话题-传统文化-009 Dragon Boat Festival 端午节

1 写作要求 为弘扬中华传统文化,增强文化自觉,学校将举行一次英语演讲比赛。请以“ ___________Festival”为题,写一篇演讲稿,介绍一个你最喜欢的中国传统节日。 提示问题: What is your favorite traditional festival? Can you say some basic facts about it? How do…

在线json调试工具

在线json格式化工具,无需登录,打开即用 https://json.openai2025.com/

在线base64工具

在线base64工具,不需登录,打开即用 base64编码和解码功能。 https://base64.openai2025.com/