题解 GD230531C【眺望】

news/2024/9/20 21:07:37/文章来源:https://www.cnblogs.com/caijianhong/p/18423275

题目描述

\(n\) 座灯塔排成一排,第 \(i\) 座灯塔的高度是 \(a_i\)。你需要求出有多少对 \(l < r\) 满足 \(a_l = a_r\),且 \(\forall l < i < r, a_i < a_l\)
灯塔的高度有 \(Q\) 次修改,每次给定 \(x, h\),表示将 \(a_x\) 修改为 \(h\)。求出修改之前和每次修改之后的答案。

\(n\leq 10^5, Q\leq 2.5\times 10^5,1\leq a_i, h\leq 10^9\)。部分分:保证只有至多 \(1000\) 座灯塔的高度发生过变化。

solution n <= 1000

单调栈随便做。。。

solution 部分分

将修改过的灯塔称为动点,没改过的灯塔称为静点。动点很少,考虑动点与动点之间、动点与静点之间的直接暴力。题解说可以 \(O(1000Q)\),可能的做法是对于动点-动点区间每次都跑单调栈,动点-静点区间可以忽略掉所有动点的影响预先跑单调栈(每个动点最多两个可能的动点-静点区间),然后维护有多少个动点正在刺破这个动点-静点区间,修改之后 \(O(Q)\) 地维护刚才说的东西。

至于静点-静点区间,这个才是大头。首先无视所有动点,单调栈跑出所有 \(O(n)\) 个区间。观察到这些区间要么不交要么包含(可能共端点,考虑连续三个相同的数),那么惯用手法就是建一棵树出来,互相包含的区间为祖孙关系,相当于整了一种很新的笛卡尔树出来,也用单调栈建。注意要使树上的每个点都代表一个区间。然后考虑动点影响,动点可能会刺穿树上的一条直链(深度递增的链),且这条链要么是空要么链底总是固定的,预处理这个链底(不一定是叶子哦),每次修改之后在树上倍增或者树剖等在树上跳,在刺穿的直链上打上标记(不覆盖是因为标记需要撤销),这部分依据实现写树剖线段树是 \(O(Q\log^2 n)\)

solution 正解

可以将部分分做法和根号重构结合一下,出题人说过不了。所以考虑线段树分治,在向下递归的过程中,越来越多的动点被固定下来转化为静点,此时要考虑这些动点对祖先的静点的影响。在每个节点都建好静点-静点的区间的树,在子树中的动点上来刺树时,用一些单调栈、双指针技巧将这些动点在树上的位置找出来,然后树剖线段树。

但是你仔细算一下这么搞的复杂度。首先算动点定位,每个点上挂的节点都要进行子树大小加深度次扫描,只要根节点上挂足够多全局静点就会 TLE。还有树剖部分,一共 \(O((n+Q)\log Q)\) 个点,每个点需要去找 \(\log Q\) 个祖先的树,各自在 \(\log Q\) 棵树上进行 \(O(\log^2n)\) 的树剖,总计 \(O((n+Q)\log^2Q\log^2n)\),直接升天。

解决方法是换个方向,考虑这个节点的树对子树内的点的贡献,因为我们知道长度为 \(l\) 的区间内至多有 \(O(l)\) 个本质不同点(即这段时间内被修改过的点,否则它不会进入这个子树),可以找出这些点,然后在树上定位它们(复杂度 \(O((n+Q)\log Q)\),算上排序再乘一个 \(O(\log n)\)),然后可以暴力在子树内 dfs 做线段树分治的过程(共 \(O(Q\log Q)\) 次调用树剖线段树)或者常数小一点的话先将涉及到的点在这段时间之前的状态处理出来,计算它们在树上的贡献,然后再扫描这段时间的所有修改。

至此本题可以在大约 \(O((n+Q)\log^2Q+Q\log Q\log^2n)\) 的时间内解决(使用树剖线段树),写全局平衡二叉树可以优化到 \(O(n\log^2n)\)(视 \(n, Q\) 同阶)。

实现细节

线段树二分

写 zkw 线段树,我们需要单点修改,找 \(x\) 前面第一个不小于 \(a_x\) 的数,\(x\) 后面第一个不小于 \(a_x\) 的数,可以看一下怎么写,这个是真的妙。

全局平衡二叉树

【模板】全局平衡二叉树 - caijianhong - 博客园 (cnblogs.com)

可参考下面的实现,感觉写的挺好。注意这里有一个线段树二分(平衡树二分?),只会在最后一次调用时发生递归,且因为二叉树深度不超过 \(O(\log n)\) 所以单次操作还是 \(O(\log n)\)

单调栈

对这些区间的某一个端点排序,稍微推导一下就能写,注意别写假了。

code

#include <bits/stdc++.h>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr, ##__VA_ARGS__)
#else
#define endl "\n"
#define debug(...) void(0)
#endif
using LL = long long;
vector<pair<int, int>> Q[1000010];
void insqry(int L, int R, int i, int h, int p, int l, int r) {/*{{{*/if (L <= l && r <= R) return Q[p].emplace_back(i, h), void();int mid = (l + r) >> 1;if (L <= mid) insqry(L, R, i, h, p << 1, l, mid);if (mid < R) insqry(L, R, i, h, p << 1 | 1, mid + 1, r);
}/*}}}*/
template <int N>
struct zkwtree {/*{{{*/ // 线段树二分int mx[N << 1];static_assert((N & (N - 1)) == 0);void mdf(int x, int v) { for (mx[x += N] = v; x >>= 1; mx[x] = max(mx[x << 1], mx[x << 1 | 1])); }int fdpre(int x) {int v = mx[x += N];for (; x; x >>= 1) if (x & 1 && mx[x ^ 1] >= v) { x ^= 1; break; }while (x && x < N) x = x << 1 | (mx[x << 1 | 1] >= v);return x && mx[x] == v ? x - N : 0;}int fdnxt(int x) {int v = mx[x += N];for (; x; x >>= 1) if (~x & 1 && mx[x ^ 1] >= v) { x ^= 1; break; }while (x && x < N) x = x << 1 | !(mx[x << 1] >= v);return x && mx[x] == v ? x - N : 0;}
};/*}}}*/
zkwtree<1 << 17> zkw;
constexpr int N = 1e5 + 10;
pair<int, int> opt[250010], H[200010];
int n, q, cnt, ans[250010], a[100010], lst[100010], stk[N], b[100010];
namespace seg { // 全局平衡二叉树(懒标记是扫描线写法)int tag[N], len[N], ch[N][2], tf[N], vl[N], vr[N], his[N], val[N];int fa[N], dfn[N], rnk[N], siz[N], son[N], dep[N], top[N];void maintain(int p) {if (tag[p]) len[p] = 0;else len[p] = len[ch[p][0]] + !his[p] + len[ch[p][1]];}int build(int l, int r) {if (l > r) return 0;int L = l, R = r, ans = l, T = siz[rnk[l]] - siz[son[rnk[r]]];while (L <= R) {int mid = (L + R) >> 1;if ((siz[rnk[l]] - siz[son[rnk[mid]]]) * 2 <= T) ans = mid, L = mid + 1;else R = mid - 1;}int p = rnk[ans];if ((ch[p][0] = build(l, ans - 1))) tf[ch[p][0]] = p;if ((ch[p][1] = build(ans + 1, r))) tf[ch[p][1]] = p;tag[p] = his[p] = 0;maintain(p);vl[p] = val[rnk[l]], vr[p] = val[rnk[r]];return p;}void binary(int p, int v, int k) {if (vl[p] <= v) return tag[p] += k, maintain(p);if (vr[p] > v) return ;if (ch[p][0]) binary(ch[p][0], v, k);if (val[p] <= v) his[p] += k;if (ch[p][1]) binary(ch[p][1], v, k);maintain(p);}int update(int p, int v, int k) {int del = 0, lst = -1;while (p) {if (ch[tf[p]][0] != p && ch[tf[p]][1] != p) del -= len[p];if (ch[p][0] != lst) {if (val[p] <= v) his[p] += k;if (ch[p][0]) binary(ch[p][0], v, k);}maintain(p);if (ch[tf[p]][0] != p && ch[tf[p]][1] != p) {del += len[p];if (vl[p] >= v) break;}lst = exchange(p, tf[p]);}return del;}vector<int> G[N];void dfs(int u, int f) {fa[u] = f, siz[u] = 1, dep[u] = dep[f] + 1, son[u] = 0;for (int v : G[u]) if (v != f) dfs(v, u), siz[u] += siz[v], siz[son[u]] < siz[v] && (son[u] = v);}void cut(int u, int topf) {dfn[u] = ++cnt, rnk[cnt] = u, top[u] = topf;if (son[u]) cut(son[u], topf);else seg::tf[seg::build(dfn[topf], dfn[u])] = fa[topf];for (int v : G[u]) if (v != fa[u] && v != son[u]) cut(v, v);}
}; // namespace seg
template <class T> void unique_proc(T arr[], int &len) {sort(arr + 1, arr + len + 1);len = (int)(unique(arr + 1, arr + len + 1) - arr - 1);
}
void build(const vector<pair<int, int>>& Qs) { // 当前区间的点与祖先的点组成的区间,建树int top = cnt = 0;for (auto e : Qs) {int i = e.first;if (int lpos = zkw.fdpre(i)) H[++cnt] = make_pair(lpos, i);if (int rpos = zkw.fdnxt(i)) H[++cnt] = make_pair(i, rpos);}unique_proc(H, cnt);for (int i = 1; i <= cnt; i++) {seg::G[i].clear(), seg::dfn[i] = 0, seg::val[i] = a[H[i].first];while (top && H[stk[top]].second <= H[i].first) --top;if (top && H[stk[top]].second >= H[i].second) seg::G[stk[top]].push_back(i);stk[++top] = i;}int cnt0 = exchange(cnt, 0);for (int i = 1; i <= cnt0; i++) if (!seg::dfn[i]) seg::dfs(i, 0), seg::cut(i, i);
}
int ont[100010], vec[350010];
void solve(int p, int l, int r) {for (auto e : Q[p]) zkw.mdf(e.first, a[e.first] = e.second);build(Q[p]);if (l == r) ans[l] += cnt;else if (cnt) {int tot = 0;for (int pd = p << 1, rd = (l + r) >> 1; l <= rd; pd <<= 1, rd = (l + rd) >> 1) {for (auto e : Q[pd]) vec[++tot] = e.first;if (l == rd) break;} // 所被修改的点ont[opt[l].first] = 0; // ont[x] 表示 x 的定位结果,注意这段时间的第一个修改可能不能对树产生影响。//for (int i = l + 1; i <= r; i++) assert(!a[opt[i].first]), vec[++tot] = opt[i].first;unique_proc(vec, tot);auto mapping = [&]() {/*{{{*/ // 动点定位int top = 0, itl = cnt, itr = tot;for (int k = 1; k <= cnt + tot; k++) {if (itl && (!itr || H[itl].first > vec[itr])) {while (top && stk[top] <= H[itl].second) ont[stk[top--]] = itl;--itl;} else stk[++top] = vec[itr--];}while (top) ont[stk[top--]] = 0;};/*}}}*/mapping();int now = cnt;for (int i = 1; i <= tot; i++) { // 处理为这段时间前的状态int x = vec[i];now += seg::update(ont[x], a[x] = b[x], +1);}if (!l) ans[l] += now;for (int i = l + !l; i <= r; i++) {int x = opt[i].first;now += seg::update(ont[x], a[x], -1); // update 返回增量,维护增量now += seg::update(ont[x], a[x] = opt[i].second, +1);ans[i] += now;}for (int i = 1; i <= tot; i++) a[vec[i]] = 0;}if (l != r) {int mid = (l + r) >> 1;solve(p << 1, l, mid);solve(p << 1 | 1, mid + 1, r);} else if (l) {b[opt[l].first] = opt[l].second;}for (auto e : Q[p]) zkw.mdf(e.first, a[e.first] = 0);
}
int main() {
#ifndef LOCALcin.tie(nullptr)->sync_with_stdio(false);  
#endifcin >> n >> q;for (int i = 1; i <= n; i++) cin >> a[i];memcpy(b, a, sizeof b);for (int t = 1, i; t <= q; t++) {cin >> i;opt[t].first = i;insqry(lst[i], t - 1, i, a[i], 1, 0, q);lst[i] = t;cin >> a[i];opt[t].second = a[i];}for (int i = 1; i <= n; i++) insqry(lst[i], q, i, a[i], 1, 0, q);memset(a, 0, sizeof a);solve(1, 0, q);for (int i = 0; i <= q; i++) cout << ans[i] << endl;return 0;
}

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

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

相关文章

opencascade Adaptor3d_CurveOnSurface源码学习

opencascade Adaptor3d_CurveOnSurface 前言用于连接由Geom包中表面上的曲线提供的服务,以及使用这条曲线的算法所要求的服务。该曲线被定义为一个二维曲线,来自Geom2d包,位于表面的参数空间中 方法 1 默认构造函数 Standard_EXPORT Adaptor3d_CurveOnSurface(); 2 通过给定…

Controller层

@RequestMapping(value = "/url",method = RequestMethod.POST) public String selectXXX(@RequestBody(required = false) String typeName){return ""; }I have a dream : Sandy beach B-J-N.

使用U盘PE重装Windows系统

1、概述 操作系统一般都是安装在硬盘内的,硬盘是一种存储数据的介质,U 盘同样也是一种存储数据的介质,因此也可以把操作系统安装进 U 盘里。 因为大部分 U 盘的性能比较差,不能流畅地运行完整版的操作系统,所以只能安装精简了大部分功能、只保留基本运行环境的简化版操作系…

反射相关API

反射的作用 在不修改源码的情况下,扩展功能。 程序在运行的时期,通过反射机制,获取类的所有内部信息,并且操作类的对象。Class类一个类在堆中只有一个Class对象,这个Class对象包含了类的完整结构信息 反射技术是针对Class对象进行操作,在程序运行的时候,动态获取类中的所…

第二十一讲:MySQL有哪些“饮鸩止渴”提高性能的方法?

第二十一讲:MySQL有哪些“饮鸩止渴”提高性能的方法? 简概引言 ​ 不知道你在实际运维过程中有没有碰到这样的情景:业务高峰期,生产环境的 MySQL 压力太大,没法正常响应,需要短期内、临时性地提升一些性能。 ​ 我以前做业务护航的时候,就偶尔会碰上这种场景。用户的开发…

C++ 数据算数类型

▲ 《C++ Primer》 P30▲ 《C++ Primer》 P38

用户验收测试指南5过渡阶段的UAT

5 UAT的位置 在本书的这一中心章节中,我们将从准备工作的细节中抽身出来,在沉浸于我们的分步方法的细节之前,先从大局出发。UAT 在更大的计划中处于什么位置?它的核心功能和属性是什么?它的总体贡献是什么? 本章涉及的主题作为一系列过渡的 IS 生命周期 过渡规划 作为过渡…

API接口12种安全措施

1. 使用HTTPS:确保数据传输过程中的安全性。2. 使用OAuth2:一种授权框架,用于授权第三方应用访问服务器上的用户数据。3. 使用WebAuthn:一种网络认证标准,用于安全地进行用户认证。4. API进行签名加密:对API请求进行签名以确保请求的完整性和真实性。5. 黑白名单:限制访…

opencascade Bnd_Range源码学习区间计算

opencascade Bnd_Range 前言这个类描述了由两个实数值限定的 1D 空间中的区间。 一个区间可以是无效的,这表示区间中不包含任何点。 方法 1 默认构造函数。创建一个无效区间。 Bnd_Range() ; 2 构造函数。创建最小最大值区间 Bnd_Range(const Standard_Real theMin, const St…

P2414 [NOI2011] 阿狸的打字机

题目思路 将每一个输出的串放入一个 Trie 树中。 考虑离线处理询问 \((x, y)\),对于每一个 \(y\) 集中处理所有的 \(x\),\(y\) 在 Trie 树上走,走过的点标记一下,结果就是 \(x\) 字符串结尾节点在 fail 树上的对应节点的子树的标记数量。 记得在节点离开的时候撤销标记。 代…

0920

线代 舒尔公式,化上三角,下三角,对角阵 范德蒙德行列式 X型行列式,{主对角中下标之和为(2k+1)的两项乘积-副对角中下标之和为(2k+1)的两项乘积【需与前面两项下标号相同】}的连乘 宽对角,a2=4bc,a2≠4bc计组 MAR位数说明存储单元位数 MDR位数说明字长 编译器:将高级语…

DeepFM

参考资料:https://blog.csdn.net/u012328159/article/details/122938925 https://blog.csdn.net/u012328159/article/details/120684544?spm=1001.2014.3001.5501DeepFM FM部分 目前在模型层面做交叉特征的难点主要有以下两个方面:交叉特征的参数独立,强依赖于在样本中的共…