Codeforces Round 881 (Div. 3) F2. Omsk Metro (hard version)(倍增+最大子段和)

原题链接:F2. Omsk Metro (hard version)


题目大意:


最初开始时,你有一个根节点 1 1 1 且权值为 1 1 1

接下来会有 n n n 个操作,每次操作按照如下格式给出:

设操作开始前节点总数为 c n t cnt cnt

  • + + + v v v x x x ,增加一个编号为 c n t + 1 cnt+1 cnt+1 ,且权值为 x ∈ { − 1 , 1 } x\in\{-1,1\} x{1,1} 的节点,同时和节点 v v v 连上一条边(保证节点 v v v 存在)。
  • ? ? ? x x x y y y k k k ,询问从 x x x y y y 的路径上,是否可以从某个位置开始,选出一些连续节点,并且使得点权之和为 k k k

解题思路:


注意到形成的结构是一棵树,并且还有一个性质: x ∈ { − 1 , 1 } x\in\{-1,1\} x{1,1}

我们引入一个结论:如果一个区间的和 s u m ≥ 0 sum\geq 0 sum0 ,那么我们可以通过删除最左边或最右边的一些元素,获得所有在区间 [ 0 , s u m ] [0,sum] [0,sum] 内的值,同样的, s u m ≤ 0 sum \leq 0 sum0 也满足这个条件。

感性证明一下为什么是对的:

首先我们肯定要从序列的一边慢慢删数,否则就不连续了。

  • 下面只考虑 s u m ≥ 0 sum \geq 0 sum0 的情况, s u m ≤ 0 sum\leq0 sum0 的情况也同理。
  • 假设序列的元素全为 1 1 1 ,结论显然是正确的。
  • 假设序列包含至少一个 − 1 -1 1 ,又因为 s u m = a × ( − 1 ) + b × 1 sum = a \times(-1)+b\times1 sum=a×(1)+b×1 ,对于每一个 − 1 -1 1,我们一定都能和某一个正 1 1 1 抵消掉这个 − 1 -1 1 的贡献,即删除了这个 − 1 -1 1 再删除一个 1 1 1 ,会使得 s u m sum sum 值不变,我们发现 − 1 -1 1 的存在本质上是没有意义的,这样问题就转化成了序列元素全是 1 1 1 的情况了。

引入这个结论有什么用呢?

假设我们可以知道这个序列 最小的负值和 S 1 S_{1} S1 以及 最大的正值和 S 2 S_{2} S2 ,按照这个结论,我们一定能用 S 1 S_{1} S1 凑出 [ S 1 , 0 ] [S_{1}, 0] [S1,0] 的任意值,用 S 2 S_{2} S2 凑出 [ 0 , S 2 ] [0,S_{2}] [0,S2] 的任意值。

所以判断是否能选出一个子数组,使得和为 k k k ,即判断 S 1 ≤ k ≤ S 2 S_{1} \leq k \leq S_{2} S1kS2 即可。

回到这题上,能否在 x → y x \rightarrow y xy 路径形成的序列上凑成 k k k ,本质就转化成了求 x → y x \rightarrow y xy 路径的最小子段和,以及最大子段和,这是一个典型问题。

假设根为 1 1 1 ,我们要求 x → y x \rightarrow y xy 的路径。

l c a lca lca x , y x,y x,y 的最近公共祖先。

假设 l c a lca lca x , y x,y x,y 的其中一个,那么答案就直接是 x → y x \rightarrow y xy 路径上的最大 / / /小子段和。

否则就是 x → l c a x \rightarrow lca xlca l c a → y lca \rightarrow y lcay 的路径拼起来,考虑怎么拼起来。

我们维护 7 7 7 个信息:

  • 严格包含 区间最左 / / /右端点的最大子段和: m x l , m x r mxl,mxr mxl,mxr
  • 严格包含 区间最左 / / /右端点的最小子段和: m n l , m n r mnl,mnr mnl,mnr
  • 只看这个区间内部的最大 / / /小子段和: m x s u m , m n s u m mxsum,mnsum mxsum,mnsum
  • 区间的和: s u m sum sum

假设我们已经知道 a → b a \rightarrow b ab b → c b \rightarrow c bc 的信息,我们要怎么将这两个区间合并成 a → c a \rightarrow c ac 的信息:

注意,这里的 a → b a \rightarrow b ab 指的是 左闭右开区间 [ a , b ) [a,b) [a,b) ,转化为下标可以理解为闭区间 [ a , b − 1 ] [a,b-1] [a,b1]

在这里插入图片描述
下面只考虑最大子段和,最小子段和也是同理:

按照上图将 a → b a \rightarrow b ab 统称为左边, b → c b \rightarrow c bc 统称为右边。

  • m x s u m a → c = max ⁡ { m x s u m a → b , m x s u m b → c , m x r a → b + m x l b → c } mxsum_{a \rightarrow c}=\max\{mxsum_{a \rightarrow b},mxsum_{b \rightarrow c}, mxr_{a \rightarrow b}+mxl_{b \rightarrow c}\} mxsumac=max{mxsumab,mxsumbc,mxrab+mxlbc}
    a → c a \rightarrow c ac 的最大子段和 m x s u m mxsum mxsum ,要么是左边或者右边其一的最大子段和,要么是左边 m x r mxr mxr 和右边 m x l mxl mxl 拼起来的中间那一段的和)

  • m x r a → c = max ⁡ { m x r b → c , m x r a → b + s u m b → c } mxr_{a \rightarrow c}=\max\{mxr_{b \rightarrow c},mxr_{a \rightarrow b}+sum_{b \rightarrow c}\} mxrac=max{mxrbc,mxrab+sumbc}
    a → c a \rightarrow c ac 包含右端点的最大子段和 m x r mxr mxr,要么是右边的 m x r mxr mxr,要么是左边的 m x r mxr mxr 再加上右边一整段区间的和)

  • m x l a → c = max ⁡ { m x l b → c , s u m a → b + m a x l b → c } mxl_{a \rightarrow c}=\max\{mxl_{b \rightarrow c},sum_{a \rightarrow b}+maxl_{b \rightarrow c}\} mxlac=max{mxlbc,sumab+maxlbc}
    a → c a \rightarrow c ac 包含左端点的最大子段和 m x l mxl mxl,要么是左边的 m x l mxl mxl,要么是左边一整段区间的和再加上右边的 m x l mxl mxl

  • s u m a → c = s u m a → b + s u m b → c sum_{a \rightarrow c} = sum_{a \rightarrow b} + sum_{b \rightarrow c} sumac=sumab+sumbc
    a → c a \rightarrow c ac 的区间的和直接就是左边的和和右边的和相加)

这样,我们就可以将树的每一条链分段维护,然后对每一段进行如上的信息合并,就能够实时求出 a → b a \rightarrow b ab 这一整条链上的信息了。

对于这一题而言,我们可以用倍增表做到在线加点维护信息,在线询问。

注意到我们的信息是自底向上维护的,且询问区间是 [ x , y ) [x,y) [x,y) 的左闭右开区间,因此我们除了 l c a lca lca x , y x,y x,y 的其中一个的情况,我们询问的答案都是 [ x , l c a ) , [ y , l c a ) [x,lca),[y,lca) [x,lca),[y,lca) 的信息。

这时我们要翻转一下一个区间,变成 [ x , l c a ) + [ l c a ] + ( l c a , y ] [x,lca)+[lca]+(lca,y] [x,lca)+[lca]+(lca,y] 的信息,才是 [ x , y ] [x,y] [x,y] 的信息。

细节比较多,可以看代码注释。

时间复杂度: O ( n log ⁡ n ) O(n \log n) O(nlogn)

AC代码:


#include <bits/stdc++.h>
#define YES return void(cout << "Yes\n")
#define NO return void(cout << "No\n")
using namespace std;using u64 = unsigned long long;
using PII = pair<int, int>;
using i64 = long long;//这里是我们的信息结构体
struct Info {int mnl, mnr, mxl, mxr, sum;int mxsum, mnsum;Info() { mnl = mnr = mxl = mxr = sum = mxsum = mnsum = 0; };Info(int _) {//初始时全为 0mnl = mnr = mxl = mxr = sum = mxsum = mnsum = 0;//分类讨论点 u 权值为 > 0 和 < 0 的情况if (_ > 0) {mxl = mxr = sum = mxsum = _;} else {mnl = mnr = sum = mnsum = _;}}//维护最大子段和friend Info merge(const Info& a, const Info& b) {Info res;res.mxsum = max({ a.mxsum, b.mxsum, a.mxr + b.mxl });res.mnsum = min({ a.mnsum, b.mnsum, a.mnr + b.mnl });res.mxl = max(a.mxl, a.sum + b.mxl);res.mxr = max(b.mxr, a.mxr + b.sum);res.mnl = min(a.mnl, a.sum + b.mnl);res.mnr = min(b.mnr, a.mnr + b.sum);res.sum = a.sum + b.sum;return res;}//区间反转操作void reverse() {swap(mnl, mnr);swap(mxl, mxr);}
};void solve() {int n;cin >> n;const int logn = __lg(n);vector<vector<int>> nxt(n + 2, vector<int>(logn + 1));vector<vector<Info>> pre(n + 2, vector<Info>(logn + 1));vector<int> dep(n + 2);vector<tuple<int, int, int>> ask;//这里懒得把代码改了,就离线了,本质上是可以做到在线的int Node = 1;pre[Node][0] = Info(1);char op; int u, v, k;for (int i = 1; i <= n; ++i) {cin >> op >> u >> v;if (op == '+') {nxt[++Node][0] = u;dep[Node] = dep[u] + 1;pre[Node][0] = Info(v);} else {cin >> k;ask.emplace_back(u, v, k);}}//对每个点跳 2^j 步的信息都做一个如上的合并//注意信息是 [x, y) 的信息for (int j = 1; j <= logn; ++j) {for (int i = 1; i <= Node; ++i) {nxt[i][j] = nxt[nxt[i][j - 1]][j - 1];pre[i][j] = merge(pre[i][j - 1], pre[nxt[i][j - 1]][j - 1]);}}//求 LCA 模板auto LCA = [&](int u, int v) {if (dep[u] < dep[v]) swap(u, v);for (int j = logn; j >= 0; --j) {if (dep[u] - (1LL << j) >= dep[v]) {u = nxt[u][j];}}if (u == v) return u;for (int j = logn; j >= 0; --j) {if (nxt[u][j] != nxt[v][j]) {u = nxt[u][j];v = nxt[v][j];}}return nxt[u][0];};//cal函数 就是通过倍增计算 [x, a) + [a, b) + ... + [z, lca) 的信息从而得到 [x, lca) 的信息//特别要注意的是如果 u == lca 会返回一个全为 0 的信息,不会对答案产生影响,具体看代码即可明白原因auto cal = [&](int u, int lca) {Info res;for (int j = logn; j >= 0; --j) {if (dep[u] - (1LL << j) >= dep[lca]) {res = merge(res, pre[u][j]);u = nxt[u][j];}}return res;};//处理每个离线出来的询问for (auto [u, v, k] : ask) {int lca = LCA(u, v);Info A = cal(u, lca), B = cal(v, lca);A = merge(A, pre[lca][0]); //将 [u, lca) + [lca, lca]A.reverse(); //翻转 [u, lca]Info T = merge(B, A); //合并 [v, lca) + [lca, u];if (T.mnsum <= k && k <= T.mxsum) {cout << "Yes\n";} else {cout << "No\n";}}
}signed main() {ios::sync_with_stdio(0);cin.tie(0), cout.tie(0);int t = 1; cin >> t;while (t--) solve();return 0;
}

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

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

相关文章

基于Java的艺培管理解决方案

✍✍计算机毕业编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java、…

用html编写的简易新闻页面

用html编写的简易新闻页面 相关代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document<…

LeetCode二叉树中的第 K 大层和

题目描述 给你一棵二叉树的根节点 root 和一个正整数 k 。 树中的 层和 是指 同一层 上节点值的总和。 返回树中第 k 大的层和&#xff08;不一定不同&#xff09;。如果树少于 k 层&#xff0c;则返回 -1 。 注意&#xff0c;如果两个节点与根节点的距离相同&#xff0c;则…

阿里面试:最佳线程数,如何确定?

尼恩说在前面 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;最近有小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、shein 希音、百度、网易的面试资格&#xff0c;遇到很多很重要的面试题&#xff1a; 如何确定系统的最佳线程数&#xff1f; 小伙伴 没有回…

SQL注入漏洞解析--less-5

1.进入第五关我们先看提示 2.还是说让我们输入一个id数字&#xff0c;那我们就输入一个先看一下 3.根据页面结果得知虽然有显示&#xff0c;但是和前面四关还是不一样是因为页面虽然有东西。但是只有对于请求出错显示&#xff0c;其余的就没有了。这个时候我们用联合查询就没有…

数据安全治理实践路线(中)

数据安全建设阶段主要对数据安全规划进行落地实施&#xff0c;建成与组织相适应的数据安全治理能力&#xff0c;包括组织架构的建设、制度体系的完善、技术工具的建立和人员能力的培养等。通过数据安全规划&#xff0c;组织对如何从零开始建设数据安全治理体系有了一定认知&…

linux提权之sudo风暴

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【Java】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收藏 …

算法--贪心

这里写目录标题 区间问题区间选点引入算法思想例题代码 最大不相交区间的数量算法思想例题代码 区间分组算法思想例题代码 一级目录二级目录二级目录二级目录 区间问题 区间选点 引入 区间问题会给定几个区间&#xff0c;之后要求我们在数轴上选取尽量少的点&#xff0c;使得…

[NOIP2011 普及组] 数字反转

AC代码&#xff1a; #include<iostream>using namespace std;int main() {long long n;cin >> n;long long temp n;long long sum 0;while(temp ! 0){int c temp % 10;sum sum * 10 c;temp temp / 10;}printf("%lld",sum);return 0; }

11个Linux性能分析命令

Linux性能分析命令有很多&#xff0c;不同的命令可以用来监控不同的系统资源和活动。根据您的问题&#xff0c;我为您推荐以下11个常用的Linux性能分析命令&#xff1a; uptime&#xff1a;显示系统的运行时间和平均负载。dmesg&#xff1a;显示系统的启动信息和内核的日志信息…

函数——递归6(c++)

角谷猜想 题目描述 日本一位中学生发现一个奇妙的 定理&#xff0c;请角谷教授证明&#xff0c;而教授 无能为力&#xff0c;于是产生了角谷猜想。 猜想的内容&#xff1a;任给一个自然数&#xff0c; 若为偶数则除以2&#xff0c;若为奇数则乘 3加1&#xff0c;得到一个新的…

Gitlab 设置页面语言为简体中文

1.用户登录&#xff0c;点击头像&#xff0c;再点击Preferences&#xff08;偏好设置&#xff09; 2.向下滑动&#xff0c;找到 Localization&#xff08;本地化&#xff09;&#xff0c;进行修改&#xff0c;并保存 3.刷新页面&#xff0c;就更改成简体中文了