首页 > 其他分享 >题解 GD230531C【眺望】

题解 GD230531C【眺望】

时间:2024-09-20 21:01:37浏览次数:1  
标签:siz 动点 log int 题解 len 静点 GD230531C 眺望

题目描述

有 \(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 LOCAL
  cin.tie(nullptr)->sync_with_stdio(false);  
#endif
  cin >> 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;
}

标签:siz,动点,log,int,题解,len,静点,GD230531C,眺望
From: https://www.cnblogs.com/caijianhong/p/18423275

相关文章

  • CF538G 题解
    CF538G题意\(s\)为长为\(l\)的由U、L、D、R组成的操作序列,一个机器人从\((0,0)\)开始按照\(s_{1\siml}\)的顺序循环行动\(+\infty\)次。给定n个形如\((t_i,x_i,y_i)\)的限制,表示第\(t_i\)时刻到达\((x_i,y_i)\)。构造\(s_{1\siml}\)。思路首先可以......
  • 力扣188-买卖股票的最佳时机 IV(Java详细题解)
    题目链接:188.买卖股票的最佳时机IV-力扣(LeetCode)前情提要:本题是由123.买卖股票的最佳时机III-力扣(LeetCode)变形而来,123是只能买卖俩次股票,该题是只能买卖k次股票,我相信当你做完这道题或者看完我上道题的题解,那么写这道题会轻松一点。因为本人最近都来刷dp类的题......
  • CF1977E 题解
    根据Dilworth定理,该图能被两条互不相交的链覆盖。从小到大加点。我们现在需要维护两个栈,每个栈维护每条链的点。若两个栈头没有连边,那么对于新加入的点,一定可以放到其中一个栈。现在唯一的问题是,新加入的点可能可以放入两个栈。我们可以再开一个栈三,用来维护以上述点为头的......
  • 【Py/Java/C++三种语言OD独家2024E卷真题】20天拿下华为OD笔试之【模拟】2024E-转骰子
    可上欧弟OJ系统练习华子OD、大厂真题绿色聊天软件戳od1441了解算法冲刺训练(备注【CSDN】否则不通过)文章目录相关推荐阅读题目描述与示例题目描述输入描述输出描述示例一输入输出说明示例二输入输出说明解题思路构建长度为6的数组表......
  • 题解:洛谷P9934 [NFLSPC #6] 绝不能忘记的事……
    题目链接:洛谷P9934[NFLSPC#6]绝不能忘记的事……我hatelove大力分讨。这道题先分三种大情况:N在左边,N在中间,N在右边。声明:以下分类讨论中,a,b,c,d均为记住的字符串。记录操作N在左边当复制串形如Nab,可以用map<string,int>记录。当复制串形如NaH,那么......
  • 【问题解决】Web在线办公系统-数据爬取结果乱码
    问题描述在【热门电影】模块,通过jsoup爬虫并解析网页数据时,执行代码,出现“中文乱码”问题。解决方法由于网页自带的编码方式与后端开发中jsoup解析的编码方式不匹配,需要修改后端解析网页的编码方式。//设置爬取网页的地址Stringurl="https://movie.douban.com/......
  • Gradio离线部署到内网,资源加载失败问题(Gradio离线部署问题解决方法)
    问题描述Gradio作为一个快速构建一个演示或Web应用的开源Python包,被广泛使用,最近在用这个包进行AI应用构建,打包部署到内网Docker的时候发现有些资源无法使用。网页加载不出来。即使加载出来了也是没有样式无法点击的。一般出现这个问题的多半是低版本的gradio,高版本中已经解决......
  • CF1526F Median Queries 题解
    Description本题是一道交互题。给定\(n\),你需要猜测一个长度为\(n\)的排列\(p\)(即\(p\)包含所有\(1\)到\(n\)的整数各一次)。已知\(p\)满足\(p_1<p_2\)。当然,你可以进行询问,每次询问你需要给定三个互不相同的整数\(a,b,c\),交互器会返回\(|p_a-p_b|,|p_b-p_c|,|p_......
  • P9705 「TFOI R1」Unknown Graph题解
    思路题目给出了每个点的初度和入度,由于图是无重边无自环的有向图。要求满足限制的图有多少条边与建图方案。我们可以考虑使用网络流中的最大流。我们知道这是一道网络流后,题目的难点就转移到网络流的建模以及输出方案的办法。网络流的建模:题目所给的条件没有源点汇点并指出......
  • P5979 [PA2014] Druzyny 题解
    对于一个固定的右端点\(r\),左端点\(l\)合法当且仅当\(\max(d_l,d_{l+1},\dotsd_r)\ler-l+1\le\min(c_l,c_{l+1},\dots,c_r)\)。容易得到一个很朴素的dp:记\(f_i\)表示前\(i\)个位置可以分成的组的数目的最大值,\(g_i\)表示有多少种分组方案能达到最大值,直接枚举左端点......