首页 > 其他分享 >P5416 笔记

P5416 笔记

时间:2024-03-09 14:56:01浏览次数:24  
标签:opt int mid 笔记 i64 P5416 calc id

前置知识:线段树分治。

题意

给定 \(n\) 个节点的树,每个节点有一个二元组集合 \(S_i\)。

这个集合有一个限制:\(S_i\) 一定是 \(S_{fa_i}\) 中删除一个二元组或者加一个二元组,并且加进来的二元组互不相同。

现在有 \(m\) 个询问,每个询问给出 \(k, h\) 表示查询 \(\min\limits_{(x, c) \in S_k}\{(x - h)^2 + c\}\)。

题解

好题啊,首先可以将式子拆成好算的形式:

\[\begin{aligned} &(x - h)^2 + cc\\ =&x^2 + h^2 - 2xh + c\\ =&h^2 + [-2xh + (x^2 + c)] \end{aligned} \]

这相当于求 \(S_k\) 中所有斜率为 \(-2x\),截距为 \(x^2 + c\) 的直线在 \(x = h\) 时纵坐标的最大值。而这是李超线段树的经典应用。于是考虑先把询问离线下来,接着 dfs 整棵树并更新这个 \(S\),再对于这个节点的每个询问一一回答即可。

可是李超线段树是不支持删除的,只支持撤销(实际上大部分数据结构都支持撤销)。于是考虑把删除转化为撤销,故使用线段树分治。所以新的问题又出现了,如何找一个直线在哪些节点上出现了。观察一下可以发现,对于所有包含同一条直线的点,这些点在树上一定是一个连通块,因为题目中保证了二元组互不相同。

所以如果将树的 dfn 序处理出来,包含一条直线的所有点的 dfn 序集合,一定是由若干区间组成的,因为每有一个删除操作,都相当于是在这条直线的区间中挖掉一部分。假设区间数量为 \(t\),易证 \(\sum t\) 是 \(O(n)\) 的。这样就可以使用线段树分治了,时间复杂度 \(O(n \log^2 n)\)。

代码

code:

#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
const int N = 5E5 + 5;
int n, m, q;
vector <int> G[N];
struct line {
  i64 k, b;
  void gen(int x, int c) {
    k = -2LL * x;
    b = 1LL * x * x + c;
  }
} li[N];
struct opr {int opt, id;} a[N];
int dl[N], dr[N], tot; vector <pair <int, int>> ed[N];
pair <int, int> be[N];
void dfs(int x, int fa) {
  dl[x] = ++tot;
  for (auto v : G[x]) {
    if (v == fa) continue;
    dfs(v, x);
  } dr[x] = tot;
  if (a[x].opt == 1) ed[a[x].id].emplace_back(dl[x], dr[x]);
  else be[a[x].id] = make_pair(dl[x], dr[x]);
}
i64 Ans[N];
vector <int> v[N << 2];
vector <pair <int, int>> qs[N];
stack <pair <int, int>> st;
struct lc_segt {
  int root, tot;
  struct node {int ls, rs, s;} t[N << 5];
  i64 calc(int u, int v) {return li[u].k * v + li[u].b;}
  void upd(int &x, int l, int r, int u) {
    if (!x) x = ++tot;
    if (t[x].s == 0) {
      st.emplace(x, 0);
      t[x].s = u;
      return ;
    } int mid = (l + r) >> 1, &v = t[x].s;
    if (calc(u, mid) < calc(v, mid)) {
      st.emplace(x, v);
      swap(u, v);
    }
    if (l == r) return ;
    if (calc(u, l) < calc(v, l)) upd(t[x].ls, l, mid, u);
    if (calc(u, r) < calc(v, r)) upd(t[x].rs, mid + 1, r, u);
  }
  i64 query(int x, int l, int r, int p) {
    if (!x || !t[x].s) return 2E18;
    i64 now = calc(t[x].s, p); int mid = (l + r) >> 1;
    if (l == r) return now;
    if (p <= mid) return min(now, query(t[x].ls, l, mid, p));
    else return min(now, query(t[x].rs, mid + 1, r, p));
  }
  void push(int id) {upd(root, -1E6, 1E6, id);}
  i64 query(int p) {return query(root, -1E6, 1E6, p);}
  void cl(int to) {
    while ((int)st.size() > to) {
      auto [x, y] = st.top(); st.pop();
      t[x].s = y;
    }
  }
} t;
void ins(int x, int l, int r, int L, int R, int id) {
  if (L > R) return ;
  if (l >= L && r <= R) {
    v[x].emplace_back(id);
    return ;
  } int mid = (l + r) >> 1;
  if (L <= mid) ins(x << 1, l, mid, L, R, id);
  if (R > mid) ins(x << 1 | 1, mid + 1, r, L, R, id);
}
void solve(int x, int l, int r) {
  int now = st.size();
  for (auto id : v[x]) t.push(id);
  int mid = (l + r) >> 1;
  if (l == r) {
    for (auto [p, id] : qs[l]) {
      i64 tmp = t.query(p);
      Ans[id] = tmp + 1LL * p * p;
    } 
  } else solve(x << 1, l, mid), solve(x << 1 | 1, mid + 1, r);
  t.cl(now);
}
signed main(void) {
  ios :: sync_with_stdio(false);
  cin.tie(nullptr); cout.tie(nullptr);
  int c0;
  cin >> n >> q >> c0; li[1].gen(0, c0); a[1].opt = 0; a[1].id = 1;
  for (int i = 2; i <= n; ++i) {
    int opt, fr, id; cin >> opt >> fr >> id;
    ++fr, ++id; G[fr].emplace_back(i);
    if (opt == 0) {int x, c; cin >> x >> c >> c >> c; li[id].gen(x, c);}
    a[i].opt = opt; a[i].id = id;
  } dfs(1, 0);
  for (int i = 1; i <= n; ++i) if (be[i].first) {
    sort(ed[i].begin(), ed[i].end());
    int nl = be[i].first;
    for (auto [l, r] : ed[i]) {
      ins(1, 1, n, nl, l - 1, i);
      nl = r + 1;
    } ins(1, 1, n, nl, be[i].second, i);
  } 
  for (int i = 1; i <= q; ++i) {
    int s, x; cin >> s >> x; ++s;
    qs[dl[s]].emplace_back(x, i);
  } solve(1, 1, n);
  for (int i = 1; i <= q; ++i) cout << Ans[i] << '\n';
}

标签:opt,int,mid,笔记,i64,P5416,calc,id
From: https://www.cnblogs.com/CTHOOH/p/18060777

相关文章

  • Java学习笔记——第十天
    面向对象高级(一)staticstatic是一个关键字,义为静态,可以修饰成员变量和成员方法。static修饰成员变量成员变量的分类类变量(静态成员变量):有static修饰的成员变量,它们属于类,在计算机里只有一份,会被类的全部对象共享。实例变量(对象成员变量):无static修饰的成员变量,属于每个对象,每......
  • Living-Dream 系列笔记 第2期
    本期主要讲解vector、map两个STL容器。知识点:首先,引入两种数组的区别:静态数组,指提前声明需要多少内存的数组,是连续的;而动态数组则是在插入元素时临时指定存储空间,不要求连续。STLvector是一个动态数组,下标默认从\(0\)开始。它支持的操作如下:定义:一维vector......
  • Living-Dream 系列笔记 第3期
    本期主要讲解二分查找。知识点二分查找:思想:分治。使用场景:在一个有序序列中,反复查找不同目标。时间复杂度:\(O(n\logn)\)。实现:对数列排序;确定二分边界(通常为L=最小下标-1,R=最大下标+1);伪代码:intL=左边界-1,R=右边界+1;while(L+1<R){intmid=(L+R)......
  • Living-Dream 系列笔记 第1期
    本期主要讲解模拟、枚举算法。例题T1简单模拟题。利用scanf/cin以int形式读入分和秒,并令秒循环累加,逢\(60\)归\(0\)并向分进\(1\),分则是逢\(24\)归\(0\)。在循环的过程中若分秒合起来是回文数字,则退出循环,按照题目格式输出当前时间。注意开始时间不算。#includ......
  • Living-Dream 系列笔记 第17期
    ProblemT1/*思路:分类讨论:若k=0,则输出x+1;若k>tot(x的位数),则输出1+k-tot个0+x;否则输出10^k+x。*/#include<bits/stdc++.h>usingnamespacestd;longlongk,x,tot,ans=1;//开longlongintmain(){ cin>>k>>x; if(k==0){cout<<x+1;return0;}//情况1......
  • Living-Dream 系列笔记 第14期
    本期主要讲解差分技巧。知识点我们令原数组为\(a_i\),则当且仅当\(d_i=a_i-a_{i-1}\)时,我们称\(d_i\)是\(a_i\)的差分数组。特别的,\(d_1=0\),\(d_{n+1}=-n\)。差分数组\(d_i\)有以下三个性质:\(d_i\)的前缀和数组为\(a_i\)。将\(a_{L\simR}\)这个区间统一加......
  • Living-Dream 系列笔记 第15期
    模拟赛爆炸祭。T1把所有连通块依次求出,若某个连通块的数量已经出现过,则说明它与以前的连通块属于同一星系,直接将星系大小加上连通块大小并取\(\max\);否则将星系数量\(+1\)。#include<bits/stdc++.h>#defineintlonglongusingnamespacestd;intn,m;intans=-1e9,num,......
  • Living-Dream 系列笔记 第11期
    本期主要讲解与上期相同内容(雾。例题T1在整个矩阵外加一圈\(0\),使得包围圈外的\(0\)形成一整个连通块。求出这个连通块并标记为\(1\),然后输出即可。#include<bits/stdc++.h>usingnamespacestd;intn;intdx[]={-1,0,1,0},dy[]={0,1,0,-1};inta[31][31],g[31][31];......
  • Living-Dream 系列笔记 第12期
    本期主要讲解一维前缀和技巧。知识点我们令\(a_i\)表示原数组的第\(i\)个元素,则\(sum_i\)表示\(a_i\)前\(i\)个元素之和,即:\[sum_i=\sum^{i}_{j=1}a_j\]我们知道,\(a\)数组前\(i\)个元素的和\(=\)前\(i-1\)个元素的和\(+a_i\)。于是便可得到\(sum\)数组的......
  • Living-Dream 系列笔记 第13期
    本期主要讲解二维前缀和。知识点我们令\(a_{i,j}\)表示原数组,则\(sum_{i,j}\)为\(a\)的二维前缀和数组。根据容斥原理,得到递推式:\[sum_{i,j}=sum_{i-1,j}+sum_{i,j-1}-sum_{i-1,j-1}+a_{i,j}\]二维前缀和适用于求静态矩阵的子矩阵元素和。若我需要求一个左上角坐标为......