首页 > 编程语言 >杭电多校算法拾遗

杭电多校算法拾遗

时间:2024-07-27 20:52:00浏览次数:10  
标签:rt 杭电多校 轻子 int ml 拾遗 nas 算法 数据结构

杭电多校算法拾遗

树上启发式合并(DSU on tree)

From D1T2 树

题意简述: 给定一棵根为 1 的树,点 \(i\) 有权值 \(A_i\)。对于每个节点 \(i\),要求计算:$$ans_i = \sum\limits_{u,v \in subtree(i)} \max(A_u, A_v) \times |A_u - A_v|$$输出 \(\mathrm{XOR}_i \ (ans_i\ \mod {2^{64}})\) 即可。

数据范围: \(n \leq 5\times 10^5, \ \ 1 \leq A_i \leq 10^6\)。

什么是树上启发式合并(DSU on tree): 我们希冀通过类似于树形 dp 的方式从下往上合并子树节点的贡献。具体地,每个根节点的答案继承了其各个分支子树内的贡献,同时还要算上跨越根节点的答案。对于前半部分,直接 dfs 即可;而对于后半部分跨越根的,假定这个答案是可用数据结构维护的,我们设想的暴力思路如下:

  • 开一个全局的数据结构,对于每个根节点,递归完子树后,再递归一遍所有后代,塞回数据结构里求解(否则这个数据结构会被其他子树覆盖);
  • 对于每个根都开一个数据结构,最后回溯时合并子树的数据,求出方案。

第一种方案是 \(O(n^2)\) 的会爆时间;第二种在本题中可应用线段树合并求解(暂不拾遗)。

我们考虑优化方案 1。观察到在第一次递归子树后不必完全清空数据结构,仍可保留一部分数据,这样第二次递归后就能少递归一些分支。以下是 树上启发式合并 的过程。

  1. 来到了一个根节点,此时数据结构中没有信息。
  2. 遍历所有轻子树,不保留轻子树 对全局数据结构的改变。遍历时要求其抹除数据,以便遍历其他子树。
  3. 最后遍历重子树,保留重子树 对全局数据结构的改变。这时数据结构中只有重子树的信息。
  4. 最后再遍历一遍轻子树,将所有轻子树后代添回数据结构中。
  5. 回到了根节点,此时数据结构中有所有子树节点。
  6. 如果我自己(根)对于父亲来说是轻子树,那么我将数据结构清零,否则直接回溯。

在算法实现中可以使用 dfn 序简化第二遍遍历轻子树过程。

算法的时间复杂度分析: 我们熟知以下定义与结论,

  • 重子树是子树大小最大的子树,轻子树为其余子树;
  • 重边是连接重子树与当前根的边,轻边是连接轻子树和当前根的边。
  • 任意节点到根的简单路径(链)上不会有超过 \(\log n\) 条轻边。

我们观察算法过程,会发现某个结点被遍历的次数就为其到根 轻边数 + 1,这是因为每次其处于轻子树时,就要被重新遍历一遍。

故我们方可得到本算法时间复杂度为 \(O(n\log n)\)。


对于本题: 数据结构是个树状数组 / 线段树即可,塞入数据结构时统计其点权前后的贡献即可。只展示核心代码。

ll n, cnt, siz[MAXN], son[MAXN], dfn[MAXN], ndf[MAXN];
ull MaxA, ai[MAXN], ans[MAXN], res = 0;
vector<ull> e[MAXN << 1];
struct RES {
    ull sm, sm2, ct;
} nas;
void dfs1(ull rt, ull fat)
{ // find out the Heavy/Light subtrees, solve DFN
    siz[rt] = 1, son[rt] = 0;
    dfn[rt] = ++cnt;
    ndf[cnt] = rt;
    for(auto to : e[rt]) {
        if(to == fat) continue;
        dfs1(to, rt);
        siz[rt] += siz[to];
        if(!son[rt] || siz[to] > siz[son[rt]]) 
            son[rt] = to;
    }
}
void calc(ull &res, RES &nas, ull nw)
{
    nas = sgt.query(1, 1, MaxA, 1, nw - 1);
    res += nw * (nw * nas.ct - nas.sm);
    nas = sgt.query(1, 1, MaxA, nw + 1, MaxA);    
    res += nas.sm2 - nw * nas.sm; 
}
void dfs2(ull rt, ull fat, bool kp)
{
    for(auto to : e[rt]) { // update Light subtree, delete data
        if(to == son[rt] || to == fat) continue;
        dfs2(to, rt, false); 
    }
    if(son[rt]) { // update Heavy subtree, maintain data
        dfs2(son[rt], rt, true);
    }
    res = 0;
    for(auto to : e[rt]) { 
        // traversal Light tree's vertices, rejoin data 
        ans[rt] += (to != fat ? ans[to] : 0);
        if(to == fat || to == son[rt]) continue;
        for(ull i = dfn[to]; i < dfn[to] + siz[to]; i++) 
        // use DFN to simplify process
            calc(res, nas, ai[ndf[i]]);
        for(ull i = dfn[to]; i < dfn[to] + siz[to]; i++) 
            sgt.update(1, 1, MaxA, ai[ndf[i]], 1);
    }
    calc(res, nas, ai[rt]);
    sgt.update(1, 1, MaxA, ai[rt], 1);
    ans[rt] += res * 2; 
    if(!kp) { // if rt a Light root itself, then delete data 
        for(ull i = dfn[rt]; i < dfn[rt] + siz[rt]; i++) 
            sgt.update(1, 1, MaxA, ai[ndf[i]], -1);
    }
}

三分

From D3T11 抓拍

题意简述: 一共 \(n\) 人,初始时第 \(i\) 人在 \((x_i, y_i)\)。每个人行走的方向有四种情况,具体地:向东走,横坐标每秒加一;向西走,横坐标每秒减一;向北走,纵坐标每秒加一;向南走,纵坐标每秒减一。每个人的行走方向不会改变,而且永不会停止。

你可以选择一个非负整数秒对所有人抓拍一张照片。该照片可以采取一个水平长方形。要求长方形内(包括边界)包括所有人。求该长方形周长的最小值。

数据范围: \(1\leq n \leq 2 \times 10^5, \ \ -10^9 \leq x_i, y_i \leq 10^9\)。

什么是三分: 类似二分,三分算法作用于一个单峰数列(函数上),能够找到该函数的峰值。名如其实,三分就是找到左右边界的三等分点,然后类二分缩小边界。

对于一段区间 \([l, r]\),定义 \(m_l\) 为靠近 \(l\) 的三等分点、\(m_r\) 为靠近 \(r\) 的三等分点,我们能求出:

\[m_l = (2l + r) /3 \\ m_r = (l + 2r) / 3 \]

拿单峰下凸函数 \(f(x)\) 举例,我们要求其 \([l, r]\) 上的极小值,则每次

\[\begin{aligned} \text{if }f(m_l) < f(m_r) \quad &l = m_l\\ \text{otherwise} \quad &r = m_r \end{aligned} \]

下面展示了 \(f(m_l) < f(m_r)\) 的例子,

三分演示

如果 \(f(m_l) < f(m_r)\),那么最低点一定不在 \([m_r, r]\) 区间内,那么便可将 \(r\) 缩小到 \(m_r\) 处。

对于另一种情况,抑或是单峰上凸找极大值的同理,画画图即可。

Trick: 对于浮点值的三分,直接三分即可;对于离散整数的三分,由于边界不好处理,在 \(l, r\) 足够接近的时候跳出暴力能够避免繁琐讨论。

// 三分模板
const double D_double = 1e-5;
const int D_int = 20;
const int INF = 0x3f3f3f3f;
double Trisection_Double(double &L, double &R, std::function<double> f)
{ // Find minimum
    double l = L, r = R, ml, mr;
    while(R - L > D_double) {
        ml = (2.0 * l + r) / 3.0;
        mr = (l + 2.0 * r) / 3.0;
        if(f(ml) < f(mr)) r = mr;
        else l = ml;
    }
    return (L + R) / 2.0; // value if f((L + R) / 2.0)
}
int Trisection_Int(int &L, int &R, std::function<int> f)
{ // Find maximum
    int l = L, r = R, ml, mr, ans, mx = INF;
    while(R - L > D_int) {
        ml = (2 * l + r) / 3;
        mr = (l + 2 * r) / 3;
        if(f(ml) < f(mr)) r = mr;
        else l = ml;
    }
    for(int i = l; i <= r; i++) {
        ans = (f(l) < mx) ? i : ans;
        mx = max(mx, f(i));
    }
    return ans;
}

回到本题: 本题需要发现周长的变化随时间是一个单峰函数。

不失一般性地,我们先仅考察在竖直方向上的边长变化。观察到,在竖直方向上,边长只受最前方和最后方的人影响。同时竖直边长也受水平方向的人影响,即水平移动方向上最远的纵坐标差值决定了竖直变长的下限。

  • 最一般的情况是最上方的人往下走,最下方的人往上走,此时竖直边长每秒减少 2;
  • 如果最上方/最下方的人没入了水平界限时,竖直边长每秒可能减少 1;
  • 如果最上方往上走,最下方往下走,竖直边长每秒可能减少 1 或 2;

所以边长的改变遵循下列顺序 \(-2 / s \rightarrow -1 /s \rightarrow 0 /s \rightarrow +1 /s \rightarrow +2 / s\)。

可能会有一些缺失,但总体成一下凸函数,水平边长类似。二者之和也下凸,运用三分即可。

标签:rt,杭电多校,轻子,int,ml,拾遗,nas,算法,数据结构
From: https://www.cnblogs.com/anjack511/p/18327444/Note-HDUacm2024-1

相关文章

  • 算法板子:滑动窗口——应用单调队列,找到窗口中的最小值与最大值
    #include<iostream>usingnamespacestd;constintN=1e6+10;inta[N];//q数组模拟单调队列;q数组存储原数组元素的下标;//递增单调队列的队头始终维护窗口中的最小值;队头存的是窗口中最小值的下标//递减单调队列的队头始终维护窗口中的最大值;队头存的......
  • C++第十一次课笔记——初始化列表算法、对象成员、静态成员
    一、初始化列表作用:C++提供初始化列表语法,用来初始化属性语法:构造函数():属性1(值1),属性2(值2),…{}classPerson{public: //传统的初始化操作 Person(inta,intb,intc){ m_A=a; m_B=b; m_C=c; } //初始化列表初始化属性 Person(inta,intb,int......
  • 排序算法--希尔排序
    希尔排序(ShellSort)是一种高效的排序算法,它是插入排序的一种改进版本(插入排序可以查看我的上一篇文章)。以下是关于希尔排序的详细讲解:基本思想希尔排序的基本思想是将原始数据集分割成若干个子序列,然后对每个子序列进行插入排序。这些子序列是由相隔一定“增量”的元素组......
  • 数据结构:算法复杂度
    目录前言数据结构和算法的基本概念数据结构和算法的重要性衡量算法的好坏时间复杂度空间复杂度例子分析例子1:冒泡排序例子2:对数时间复杂度总结前言在编程学习中,理解数据结构和算法是至关重要的。这不仅是计算机科学的基础知识,也是解决复杂问题和优化代码效率的关......
  • 算法训练 2024.7.27 17:25
    目录1.两数之和2.反转链表3.是否为有效的括号4.最长公共前缀5.合并两个有序数组6.岛屿的个数7.最小路径和8.三数之和9.计数质数10.字符串转换整数(atoi)1.两数之和题目:给定一个整数数组nums和一个目标值target,请你在该数组中找出和为目标值的那两个整......
  • 代码随想录算法训练营第48天 | 序列问题最终篇
    115.不同的子序列https://leetcode.cn/problems/distinct-subsequences/description/代码随想录https://programmercarl.com/0115.不同的子序列.html#算法公开课https://leetcode.cn/problems/delete-operation-for-two-strings/description/https://programmercarl.com/05......
  • 代码随想录算法训练营第47天 | 动态序列11:序列专题2
    代码随想录算法训练营第天|1143.最长公共子序列https://leetcode.cn/problems/longest-common-subsequence/description/代码随想录https://programmercarl.com/1143.最长公共子序列.html#算法公开课1035.不相交的线https://leetcode.cn/problems/uncrossed-lines/descrip......
  • 决策树算法详解:原理、实现与应用案例
    目录一:简介二:决策树算法原理决策树的基本概念信息增益和熵基尼指数卡方检验三:决策树的构建过程数据预处理决策树生成算法剪枝技术决策树的优缺点四:决策树算法的实现使用Python实现决策树使用R语言实现决策树实现过程中需要注意的问题五:决策树算法的优化与改进......
  • 2024AGI面试官 常问的问题以及答案(附最新的AI大模型算法面试大厂必考100题 )
    前言在这个人工智能飞速发展的时代,AI大模型已经成为各行各业创新与变革的重要驱动力。从自动驾驶、医疗诊断到金融分析,AI大模型的应用场景日益广泛,为我们的生活带来了前所未有的便捷。作为一名程序员,了解并掌握AI大模型的相关知识,无疑将大大提升我们的竞争力。在这个充满......
  • 深度解析Memcached:内存分配算法的优化之旅
    ......