Luogu 3642 [APIO 2016] 烟火表演

2023-05-16

          • 传送门
          • 引例(上一道题)
          • 凸函数
          • 一开始的思路
          • 正解
          • 参考代码
          • 总结

传送门
引例(上一道题)
凸函数

  回忆我们上一道题是怎么做的。我们维护的东西的实质是一个(下)凸函数。由于我们的操作相当于是加上一个凸函数,而凸函数与凸函数的和仍然为凸函数。我们只保存斜率发生变化的点,利用题目给函数带来的特殊性质,只用这些点的横坐标就计算出了答案。

一开始的思路

  不难发现,如果最后的时间过长,那么一定会浪费;如果最后时间过短,那么也会有不必要的缩短,感性地理解,好像二分答案可做。不过我们没有办法进行可行性检验。

正解

  根据前面的思路,我们可以发现:若设 fi(x) f i ( x ) 表示以 i i 为根的子树需要 x 秒去引爆时的最小代价,那么 fi(x) f i ( x ) 是一个(下)凸函数。

  我们考虑在子树 u u 的根结点上加上它父亲连向它的一条边权为 w 的边对 fu(x) f u ( x ) 的影响。我们先设 [L,R] [ L , R ] 表示 fi(x) f i ( x ) 斜率为 0 0 的那一段的左右端点。

fu(x)={fu(x)+wxL让新的边权为 0fu(L)+(w(xL))L<xL+w让新的边权为 xLfu(L)L+w<xR+w让新的边权为 wfu(L)+((xR)w)R+w<x让新的边权为 xR

  其中 fu(x) f u ( x ) 表示新函数, fu(x) f u ′ ( x ) 表示原函数。显然,最后的答案为 f1(x1) f 1 ( x 1 ) ,其中 xi x i 表示使得 fi(x) f i ( x ) 取得最小值的 x x


  我们先来理解下为什么状态转移方程是这样的。首先需要注意的是,我们设的 x 与上一道题的 x x 并不一样,所以转移的方法肯定也不一样。

  可以知道,x=x+w(w0),其中 x x ′ 表示原来引爆的总时间, w w ′ 表示最后决定把这条边的边权从 w w 变成 w。当 xL x ≥ L 时,我们要令 w w ′ 越小越好,因为当 x x ′ 越小(即 w w ′ 越大)时,为了让原来引爆的总时间变成 x x ′ 的代价就越高,且斜率 1 ≤ − 1 ,而修改 w w 的单位代价仅为 1,所以这并不划算,因此我们令 w=0 w ′ = 0 。对于第二部分,我们保证 x=L x ′ = L 就好,这样我们可以少减少 w w 。由于最终 w=xL(这样才有 x=L x ′ = L ),因此花费的代价是 w(xL) w − ( x − L ) 。对于第三部分,我们不用改变 w w 都保证了 LxR,因此不需要操作。对于第四部分,由于越往后斜率越大,且至少为 1 1 ,而我们修改 w 的单位代价为 1 1 ,同时我们可以无限制地加长 w,因此我们选择让 x=R x ′ = R ,所以花费的代价就是 (xR)w ( x − R ) − w


  我们来仔细分析一下这个过程究竟干了什么。首先很明显,对于 xL x ≤ L 的部分,我们把它向上平移了 w w 个单位。对于第二部分,我们从第一部分的结尾开始画了一条斜率为 1 ,(横坐标)长度为 w w 的直线,到第三个部分,我们接着画了一条斜率为 0,长度为 RL R − L 的直线,再到第四个部分,我们接着画了一条斜率为 1 1 的直线,一直延伸到正无穷……

  有没有感觉跟上一道题的图形有点像?可以发现,各关键点间形成的直线的斜率是递增的。但是这个递增对我们来说暂时还没有什么用,毕竟到这里我都还完全做不来。我们应该仔细看看这个函数有没有其它性质,因为我们能够维护的只有拐点的横坐标,我们必须通过这些横坐标算出我们想要的信息(就像上一题一样,我们把答案拆开来算,因此就不用管最后的函数值了)。


  我们发现:利用上面的函数进行转移时,函数的斜率一定会是 ,3,2,1,0,1,如果某个斜率不存在,那也一定在某个位置有两个重合的点,我们把它视作一个长度为 0 0 的区间,认为它仍然存在。

证明
  当一开始这个函数为空时,进行转移相当于是叶结点接上了父结点,这时,我们得到了斜率为 1 1 1 的两条直线,我们视它中间存在一条长度为 0 且斜率为 0 0 的直线。
  当我们对一个符合条件的函数进行转移时,我们相当于是在斜率为 1 和斜率为 0 0 的直线的拐点处增加了一条斜率为 1 的直线,这并不影响函数的这个性质。故这个性质成立。

  为什么要规定函数有这么一个性质(你有没有觉得我们的强行规定使得这个东西很没说服力?)?因为 f(0) f ( 0 ) 是相当好求的:就是所有导火索的原长度之和。如果我们知道了所有拐点的位置,我们只需要减去一定的值,就能算出 f(L) f ( L ) 了。

  像上一道题一样,我们得到了只需要拐点坐标就求得所需函数值的方法,岂不美哉?


  考虑程序实现。对于叶结点,我们只需要在 w w 处插入两个点就好了,它左边代表一条斜率为 1 的直线,中间代表一条斜率为 0 0 的直线(长度为 0),右边代表一条斜率为 1 1 的直线。对于一棵树,我们将所有儿子对应子树的函数加起来就好了。不难发现,通过保存拐点,函数仍然满足前面我们规定的性质。那么,怎么维护函数取得最小值时的横坐标呢?

  这需要结合我们的转移对函数的影响进行考虑。我们的转移只会增加一条斜率为 1 的直线,并且我们用最右边的拐点对它进行表示。当多个函数加起来时,有多少个函数,斜率的最大值就为多少。我们在维护时保留 R R 端点,那么我们只需要在合并后删除坐标最大的 k 个拐点就好了, k k 代表儿子个数。(但是为了方便,为了让叶结点和非叶结点进行统一,我们要保存 R,也就是说只删除 k1 k − 1 个拐点就可以了)

  那么怎么考虑非叶结点的转移呢?由于插入了一条斜率为 1 − 1 ,长度为 w w 的直线,因此斜率为 0 的那一段相当于是向右平移了 w w 个单位。我们删除原来的拐点,重新插入在新的位置即可(因为 0 左边的直线斜率为 1 − 1 ,所以不用再插入新的拐点,相当于是斜率为 1 − 1 的直线变长了)。

  最后,我们只保留 L L 及其左边的拐点,然后依次减去它们的横坐标,正好就是我们要的函数值。

(灵魂之图)

参考代码
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <cassert>
#include <cctype>
#include <climits>
#include <ctime>
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <stack>
#include <queue>
#include <deque>
#include <map>
#include <set>
#include <bitset>
#include <list>
#include <functional>
typedef long long LL;
typedef unsigned long long ULL;
using std::cin;
using std::cout;
using std::endl;
typedef LL INT_PUT;
INT_PUT readIn()
{
    INT_PUT a = 0; bool positive = true;
    char ch = getchar();
    while (!(ch == '-' || std::isdigit(ch))) ch = getchar();
    if (ch == '-') { positive = false; ch = getchar(); }
    while (std::isdigit(ch)) { a = a * 10 - (ch - '0'); ch = getchar(); }
    return positive ? -a : a;
}
void printOut(INT_PUT x)
{
    char buffer[20]; int length = 0;
    if (x < 0) putchar('-'); else x = -x;
    do buffer[length++] = -(x % 10) + '0'; while (x /= 10);
    do putchar(buffer[--length]); while (length);
    putchar('\n');
}

template <typename T, typename C = std::less<T> >
class priority_queue
{
    struct Node
    {
        T v;
        int dis;
        Node* ch[2];
        Node() : v(), dis(-1), ch() {}
        Node(const T& v) : v(v), dis(0), ch() {}
    };
    Node* root;
    int s;

private:
    void operator=(const priority_queue&) {} // 禁止拷贝

public:
    priority_queue() : root(), s() {}
    ~priority_queue() { clear(); }

private:
    void clear(Node* &r)
    {
        if (!r) return;
        clear(r->ch[0]);
        clear(r->ch[1]);
        delete r;
        r = NULL;
    }
public:
    void clear() { clear(root); }

public:
    int size() { return s; }
    bool empty() { return !s; }

    // significant below
private:
    static Node* merge(Node* a, Node* b)
    {
        if (!b) return a;
        if (!a) return b;
        if (C()(b->v, a->v)) std::swap(a, b);
        a->ch[1] = merge(a->ch[1], b);
        if (!a->ch[0] || (a->ch[1] && a->ch[1]->dis > a->ch[0]->dis)) // note
            std::swap(a->ch[0], a->ch[1]);
        if (a->ch[1])
            a->dis = a->ch[1]->dis + 1;
        else
            a->dis = 0;
        return a;
    }
public:
    void merge(priority_queue& b)
    {
        root = merge(root, b.root);
        s += b.s;
        b.root = NULL;
        b.s = NULL;
    }

public:
    void push(const T& x)
    {
        root = merge(root, new Node(x));
        s++;
    }
    const T& top() const
    {
        return root->v;
    }
    void pop()
    {
        Node* del = root;
        root = merge(root->ch[0], root->ch[1]);
        delete del;
        s--;
    }
};

// if (x <= L) f[x] += w; // 让新的边权为 0
// if (L < x && x <= L + w) f[x] = f[L] + (w - (x - L)); // 让新的边权为 x - L
// if (L + w < x && x <= R + w) f[x] = f[L]; // 让新的边权为 w
// if (x > R + w) f[x] = f[L] + ((x - R) - w); // 让新的边权为 x - R

const int maxn = int(6e5) + 5;
int n, m, E;
int degree[maxn];
int parent[maxn];
int weight[maxn];
priority_queue<long long, std::greater<long long> > pq[maxn];
LL sum;

void run()
{
    n = readIn();
    m = readIn();
    for (int i = 2; i <= n + m; i++)
    {
        degree[parent[i] = readIn()]++;
        sum += weight[i] = readIn();
    }
    for (int i = n + m; i > 1; i--)
    {
        long long l = 0, r = 0;
        if (i <= n)
        {
            for (int j = 1; j < degree[i]; j++)
                pq[i].pop(); // 最后留下了 L 和 R
            r = pq[i].top();
            pq[i].pop();
            l = pq[i].top();
            pq[i].pop();
        }
        pq[i].push(l + weight[i]);
        pq[i].push(r + weight[i]);
        pq[parent[i]].merge(pq[i]); // 合并到父结点
    }
    for (int i = 1; i <= degree[1]; i++)
        pq[1].pop(); // 最后只剩下了 L
    while (pq[1].size())
    {
        sum -= pq[1].top(); // 依次减去横坐标,正好就是函数值
        pq[1].pop();
    }
    printOut(sum);
}

int main()
{
    run();
    return 0;
}
总结

  一道思维很深邃却又透露出套路的下凸函数的题目。这类题,首先你要发现下凸性质,其次你需要想清楚该怎么转移,更重要的,你需要想清楚在只保存拐点(关键点)的情况下如何计算出答案:是利用转移的性质边算边累计(如第一题)?还是利用函数性质最后来算(如这一题)?取决于你有没有观察出题目的性质了……一般来说,维护拐点通常都选择使用大根堆,把斜率大于 0 的点给删去了(至少我做的唯一两道题是这样),如果遇到树(比如这道题),可以用可并堆。两个(下)凸函数相加后还是凸函数,且可能还满足某些别的性质(比如这道题)。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Luogu 3642 [APIO 2016] 烟火表演 的相关文章

  • Ubuntu中/usr/local 和 ~/.local 之间的区别

    Ubuntu中 usr local 和 local 之间的区别 usr local 是一个可供所有用户使用的软件可由管理员安装的地方 local bin 是一个用户可以安装软件供自己使用的地方 不同发行版和社区使用的目录结构的历史有些混乱
  • Centos7配置yum镜像源(base,extras,updates,epel,local)

    一 备份默认源 由于默认源都在国外 xff0c 速度非常慢 xff0c 需要把默认的源配置文件备份后删除 span class token comment 进入配置文件目录 span span class token function cd
  • win10彻底关闭windows update 自动更新的方法

    转载自 xff1a https jingyan baidu com article 6181c3e0d75aaa152ef15326 html 其实保留更新还是很有用的 xff0c 毕竟官方一直在修复漏洞 但是服务器虚拟机中运行的win10
  • 解决Centos 7 VNC黑屏

    在配置Centos 7下VNC时发现root用户可以正常登陆VNC桌面 xff0c 而普通用户VNC桌面黑屏 xff0c 分析 vnc xstarup 后发现是普通用户没有执行 etc X11 xinit xinitrc的权限 bin sh
  • 一个与cni0相关的pod创建问题

    今天查看k8s xff0c 发现有个coredns的pod创建失败 xff0c 查看这个POD的信息 xff0c 显示如下错误 combined from similar events Failed to create pod sandbo
  • Debian10安装SSH、配置NTP、安装配置UFW防火墙、配置PATH

    一 SSH安装配置 1 1 安装SSH span class token comment 安装SSH客户端 span apt span class token function install span openssh client spa
  • Debian10 创建用户、用户组、切换用户

    span class token comment 新建用户组 span span class token function groupadd span hausers span class token comment 新建用户并加入用户组
  • C++关于循环依赖的问题

    C 43 43 关于循环依赖的问题 xff1a 循环情况 xff1a class B class A public B b class B public A a 若两个类之间存在循环依赖则在编译时会报错 xff0c 原因是两个类中存在相互的
  • Rust小项目一:Rust 网络编程,实现一个Tcp server

    近日学习Substrate的开发入门 xff0c 之前没有接触过Rust编程 xff0c 今天跟着视频做个小项目练练手 项目目标 xff1a 编写一个Tcp server端与一个Tcp client端 xff0c 客户端中输入内容后 xff
  • Python 项目打包并发布到私有 PyPI 服务器

    推广博客 xff1a Python 项目打包并发布到私有 PyPI 服务器
  • C++ 零碎特性

    摘自 C 43 43 17 入门经典 几乎不会再更新 文章目录 使用花括号初始化变量零初始化使大整型字面量更加易读二进制的整型字面量 96 size t 96 类型浮点数的特殊情况 xff1a NaN xff08 Not a Number
  • Python 学习笔记——进阶

    文章目录 一 模块 xff08 一 xff09 1 导入外部模块2 导入时重命名3 标准库 xff08 一 xff09 96 sys 96 96 argv 96 变量 96 exit 96 函数 96 modules 96 变量 96 pa
  • C++ UTF-8 编码与 UTF-32 编码的互相转换

    C 43 43 UTF 8 编码与 UTF 32 编码的互相转换 代码实现基本照搬了秦建辉的博客 这里不介绍原理 xff0c 只提供可以直接使用的代码 要求 C 43 43 编译器的语言标准至少为 C 43 43 17 如果编译器支持的语言
  • C++ 默认移动构造函数的调用情况

    C 43 43 默认移动构造函数的调用 直接上测试代码 xff1a include lt cstdio gt include lt iostream gt include lt string gt class MyClass int a 6
  • LaTeX 003:使用 MikTeX 组件实现 pdf 转 eps

    气死了气死了 xff0c 这玩意儿居然直接百度不到一个很好的答案 xff0c 百度到的全是用带图形化界面的软件 xff0c 您不累吗 xff1f 唯一找到的一个 xff0c 效果不好 xff0c 是糊的 最后还是上谷歌镜像站 xff0c 一
  • 【深入浅出ios开发】UIStoryboardSegue详解

    一个UIStoryboardSegue对象负责执行两个试图控制器之间的视觉过渡 另外 xff0c segue对象通常用来准备从一个控制器过渡到另一个控制器 segue对象包含了涉及过渡的控制器的信息 当segue被触发 xff0c 并且在视
  • C++ 多线程编程导论(上)

    随着摩尔定律逼近失效和多核处理器快速发展 xff0c 多线程编程变得越来越重要 本文将系统介绍在 C 43 43 中如何使用 STL 实现多线程编程 多线程编程博大精深 xff0c 本文并不介绍多线程算法或多线程编程方法 xff0c 而是把
  • 使用 WaitForSingleObject 等待进程结束错误代码 5(拒绝访问)问题的解决

    说明使用 OpenProcess 的权限不够 xff0c 但是哪个权限呢 xff1f 查阅文档发现是 SYNCHRONIZE SYNCHRONIZE 0x00100000L 使用对象的同步机制的权限 该权限允许线程等待该对象 xff0c 直
  • LaTeX 004:隐藏 hyperref 超链接的红框

    针不戳 xff0c 很快找到答案了针不戳 以往的方法是设置超链接红框的颜色 hypersetup pdfauthor 61 UnnamedOrange colorlinks 61 true linkcolor 61 black anchor
  • LaTeX 005:删除一个命令

    C 43 43 的预编译系统中 xff0c 可以使用 undef 来取消 xff08 Undefine xff09 一个宏 LaTeX LaTeX L A T E X 中是否有类似于这样的功能呢 xff1f 网上的一个评论给出了解决方案 x

随机推荐

  • LaTeX 006:添加一个只有文字没有标号的脚标

    想要如下的效果 xff1a 该脚标只想要写一句注释 xff0c 没有对应的正文文本 如何实现 xff1f 直接使用 footnotetext 是不佳的 xff0c 前面会加上当前的脚标标号 xff0c 而且该标号不会自动递增 虽然 foot
  • GetExitCodeProcess 所需运行时间

    GetExitCodeProcess 在 Windows 中用于获取进程的返回代码 这个看上去只有一个读操作的函数运行速度如何呢 xff1f 直觉上不会很快 xff0c 因为它肯定涉及操作系统进程表的操作 下面做了一个实验 代码 xff1a
  • C++ 继承时返回值或参数的类型是父类的行为

    见以下代码的 base t amp operator 61 const base t amp another 还没有搞清楚 span class token macro property span class token directive
  • LaTeX 007:texify 调用 zhmakeindex

    如文档所述 xff0c 在系统增加一个值为 zhmakeindex 路径的环境变量 MAKEINDEX 即可
  • 转载:LaTeX 定义参数变长的命令

    本文作者 xff1a Liam Huang 本文链接 xff1a https liam page 2017 07 30 define a new command with different amount of parameters in
  • 一个简单的 Lex 词法分析程序示例

    作为一个学习 Lex 词法分析程序的例子 xff0c 下面的 lex 程序将会生成一个分析 LaTeX 中命令的词法分析器 下面的程序包含了很多 lex 语言的语法 xff0c 正则表达式除外 正则表达式的用法网上比较多 xff0c 这里不
  • mysql数据库conf配置详解

    mysqld port 61 6033 skip grant tables datadir 61 usr tools mysql data socket 61 usr tools mysql mysql sock user 61 mysql
  • LaTeX 008:比较方便的键入下划线的方式

    在 LaTeX 中 xff0c 我们有时会需要输入下划线 直接键入 是不行的 xff0c 会出现的编译错误 xff0c 正如网友所述 xff0c LaTeX 为了简化对编译错误的处理禁止在文本模式 xff08 text mode xff09
  • LaTeX 009:自定义带有 * 号的命令

    LaTeX 中 xff0c 我们经常见到 section 和 section xff0c 分别表示有编号的 section 和没有编号的 section 我们也想自己定义带有 号的命令 xff0c 但写下面的代码时却报错了 xff1a ne
  • 2022 New Year‘s Resolution

    Some Might Say 2022 New Year 39 s Resolution Some might say we are on the edge of the new era Always are they saying thi
  • C++ 多线程编程导论(中)

    受篇幅限制 xff0c 上半部分不再更新 xff0c 填坑的新内容都放在此文章中 文章目录 参考资料线程安全 xff08 续 xff09 互斥访问 互斥体 xff08 mutex xff09 和锁 xff08 lock xff09 什么是互
  • C++ 使用模板序列化/反序列化固定键值对

    仅是一个原型 xff0c 留作记录 我感觉可以写出非常逆天的代码 span class token macro property span class token directive hash span span class token d
  • 编译原理习题两则(龙书,写出语言的正则定义)

    3 3 5 3 注释 xff0c 即 和 之间的串 xff0c 且串中没有不在双引号 xff08 34 xff09 中的 注 xff1a 假设双引号是匹配的 思路 xff1a 从空串开始写 xff0c 写出整体框架后 xff0c 通过分类讨
  • 2023 New Year‘s Resolution

    This Is Game 2023 New Year 39 s Resolution My 2022 ended with a day of game I am convinced that I am not to blame becaus
  • 补录:2018 和 2019 New Year‘s Resolution

    前言 xff1a 吉光片羽 xff0c 以飨读者 2018 New Year 39 s Resolution One year and a half ago I felt that life in 2020 would be quite d
  • 原博文地址

    由于账号问题 xff0c 现更改为这个账号 xff0c 以下为原博文地址 使用WH MOUSE LL钩子来判断按键是否是mouse event模拟的 http blog csdn net qq 26140973 article detail
  • [NOI 2003] 文本编辑器 Splay 维护序列 / 块状链表

    传送门 xff08 JZOJ xff09 xff08 第一道全国决赛题 xff09 解法 1 xff1a 使用 Splay 维护 不管怎么说 xff0c 总和刚刚学过的迎合上了 这道题可以直接上 Splay 维护线性序列 xff0c 光标位
  • 一次macOS的升级填坑(macOS Catalina - macOS Monterey)

    目录 小序一 升级前操作二 升级中三 问题填坑1 像我一样长时间卡在一个进度条怎么办2 在更新途中重启过电脑 xff08 完整流程填坑 xff09 3 安装之后不能开机 xff0c 如何紧急拷贝资料4 安装不成功 xff0c 如何重新安装系
  • CF 713C Sonya and Problem Wihtout a Legend

    文章目录 传送门题目大意正解通过维护关键点来维护信息参考代码 传送门 题目大意 给定一个长度为 n n 3000
  • Luogu 3642 [APIO 2016] 烟火表演

    传送门引例 xff08 上一道题 xff09 凸函数一开始的思路正解参考代码总结 传送门 引例 xff08 上一道题 xff09 凸函数 回忆我们上一道题是怎么做的 我们维护的东西的实质是一个 xff08 下 xff09 凸函数 由于我们的