[Luogu P3642] [BZOJ 4585] [APIO2016]烟火表演

本博客探讨了在烟花表演中通过调整导火索长度实现所有烟花同时爆炸的问题,涉及导火索布局优化、代价计算及算法设计。
洛谷传送门
BZOJ传送门

题目描述

烟花表演是最引人注目的节日活动之一。在表演中,所有的烟花必须同时爆炸。为了确保安全,烟花被安置在远离开关的位置上,通过一些导火索与开关相连。导火索的连接方式形成一棵树,烟花是树叶,如图所示。火花从开关出发,沿导火索移动。每当火花抵达一个分叉点时,它会扩散到与之相连的所有导火索,继续燃烧。导火索燃烧的速度是一个固定常数。图中展示了六枚烟花 {E1,E2,,E6} { E 1 , E 2 , … , E 6 } 的连线布局,以及每根导火索的长度。图中还标注了当在时刻 0 0 从开关点燃火花时,每一发烟花的爆炸时间。

img

Hyunmin 为烟花表演设计了导火索的连线布局。不幸的是,在他设计的布局中,烟花不一定同时爆炸。我们希望修改一些导火索的长度,让所有烟花在同一时刻爆炸。例如,为了让图中的所有烟花在时刻 13 爆炸,我们可以像下图中左边那样调整导火索长度。类似地,为了让图中的所有烟花在时刻 14 14 爆炸,我们可以像下图中右边那样调整长度。

img

修改导火索长度的代价等于修改前后长度之差的绝对值。例如,将上面那副图中布局修改为下面那副图的左边布局的总代价为 6 6 ,而修改为右边布局的总代价为 5

导火索的长度可以被减为 0 0 ,同时保持连通性不变。

给定一个导火索的连线布局,你需要编写一个程序,去调整导火索长度,让所有的烟花在同一时刻爆炸,并使得代价最小。

输入输出格式

输入格式:

所有的输入均为正整数。令 N 代表分叉点的数量, M M 代表烟花的数量。分叉点从 1 N N 编号,编号为 1 的分叉点是开关。烟花从 N+1 N + 1 N+M N + M 编号。

输入第一行为 N,M N , M 。后面 N+M1 N + M − 1 行,第 i i 行两个整数 Pi+1,Ci+1 。其中 Pi P i 满足 1Pi<i 1 ≤ P i < i ,代表和分叉点或烟花 i i 相连的分叉点。 Ci 代表连接它们的导火索长度( 1Ci109 1 ≤ C i ≤ 10 9 )除开关外,每个分叉点和多于 1 1 条导火索相连,而每发烟花恰好与 1 条导火索相连。

输出格式:

输出调整导火索长度,让所有烟花同时爆炸,所需要的最小代价。

输入输出样例

输入样例#1:
4 6
1 5
2 5
2 8
3 3
3 2
3 3
2 9
4 4
4 3
输出样例#1:
5

说明

【数据规模】

子任务 1(7 分): N=1 N = 1 , 1M100 1 ≤ M ≤ 100

子任务 2(19 分): 1M300 1 ≤ M ≤ 300 ,且开关到任一烟花的距离不超过 300 300

子任务 3(29 分): 1M5000 1 ≤ M ≤ 5000

子任务 4(45 分): 1M300000 1 ≤ M ≤ 300000

解题分析

神题啊QAQ蒟蒻搞了好久才弄明白…

首先我们考虑 fA(x) f A ( x ) 表示将 A A 点子树内所有烟火爆炸时间变为x的花费,显然这是个下凸的函数,并且 fA(0)=leafisubtree[A]dis[i] f A ( 0 ) = ∑ l e a f i ∈ s u b t r e e [ A ] d i s [ i ]

然后我们考虑从 fA(x) f A ( x ) 转移到 ffat[A](x) f f a t [ A ] ( x ) 的过程。设 [L,R] [ L , R ] f(x) f ( x ) 上最低的一段, dis[A] d i s [ A ] Afat[A] A → f a t [ A ] 的长度, fA(x) f A ′ ( x ) 为转移后的函数。

  • 对于 x[0,L] x ∈ [ 0 , L ] ,显然我们要将新的导火索清零, 所以这一段函数向上平移 dis[A] d i s [ A ] ,即 fA(x)=fA(x)+dis[A] f A ′ ( x ) = f A ( x ) + d i s [ A ]
  • 对于 x[L+1,L+dis[A]1] x ∈ [ L + 1 , L + d i s [ A ] − 1 ] ,我们可以不修改以前的导火索,只减短新的导火索的长度。 fA(x)=fA(x)+dis[A](xL) f A ′ ( x ) = f A ( x ) + d i s [ A ] − ( x − L )
  • 对于 x[L+dis[A],R+dis[A]] x ∈ [ L + d i s [ A ] , R + d i s [ A ] ] ,这一截就是原来的 [L,R] [ L , R ] 转移过来的,所以 fA(x)=fA(L) f A ′ ( x ) = f A ( L )
  • 对于 x[R+dis[A]+1,+) x ∈ [ R + d i s [ A ] + 1 , + ∞ ) ,我们可以加长新的导火索的长度,所以 fA(x)=fA(R)+x(R+dis[A]) f A ′ ( x ) = f A ( R ) + x − ( R + d i s [ A ] )

也就是说大概是这个样子(dis[A]=2的情况):

可以发现其实就是把 fA(x) f A ( x ) 斜率非负的一半砍掉,另一半向上平移, 再加入斜率为 1,0,1 − 1 , 0 , 1 的三段直线。

考虑如何合并几个凸包的 f(x) f ′ ( x ) 从而得到 ffat[A](x) f f a t [ A ] ( x ) 。大概就像下面这样:

然后我们可以发现,对于合并后的凸包,每个原来的节点都会使斜率 +1 + 1 (其实上图的 ABCDE A → B → C → D → E 这个凸包的 B B 点少画了一个重合的点)。所以我们用一个可并堆直接暴力合并子树中的凸包。

那么如何得到合并后凸包的最小值?我们可以发现合并后的凸包的斜率为正部分的点数就是子树的个数,所以直接弹出son[fat[A]]次即可。

但这样处理后我们只得到了 [L,R] [ L , R ] 区间,并没有得到对应的花费。这时有一个更妙妙的性质:因为每个点都会使凸包斜率+1,所以考虑一个在 xi x i 位置的点,其对花费的贡献是 xi − x i ,所以我们一边弹堆一边用总路径和(即为 f1(x) f 1 ( x ) )减去堆顶的元素。

(如果还有不太懂的同学可以画画图演示,博主反正是画图才懂QAQ)

下面是极其简短的代码(pb_ds大法好

#include <cstdio>
#include <algorithm>
#include <cstdlib>
#include <cctype>
#include <cmath>
#include <cstring>
#include <ext/pb_ds/priority_queue.hpp>
#define R register
#define IN inline
#define gc getchar()
#define W while
#define MX 300050
#define ll long long
template <class T>
IN void in(T &x)
{
    x = 0; R char c = gc;
    W (!isdigit(c)) c = gc;
    W (isdigit(c))
    x = (x << 1) + (x << 3) + c - 48, c = gc;
}
__gnu_pbds::priority_queue <ll> que[MX];
int son[MX], fat[MX], dis[MX], rop, fir;
ll sum;
int main(void)
{
    ll lef, rig;
    in(rop), in(fir); int bd = rop + fir;
    for (R int i = 2; i <= bd; ++i)
    {
        in(fat[i]), in(dis[i]);
        son[fat[i]]++; sum += dis[i];
    }
    for (R int i = bd; i >= 2; --i)
    {
        lef = rig = 0;
        if(i <= rop)
        {
            W (--son[i]) que[i].pop();
            lef = que[i].top(), que[i].pop();
            rig = que[i].top(), que[i].pop();
        }
        que[i].push(lef + dis[i]), que[i].push(rig + dis[i]);
        que[fat[i]].join(que[i]);//配对堆骚操作,直接合并
    }
    W (son[1]--) que[1].pop();
    W (!que[1].empty()) sum -= que[1].top(), que[1].pop();
    printf("%lld", sum);
}
### 题目解析 洛谷 P1645 序列问题描述为给定若干个区间,每个区间有对应的整数数量要求,需要在满足这些区间内整数数量要求的情况下,求出最少需要选择多少个整数。该问题可以使用差分约束系统来解决。差分约束系统是一种特殊的不等式组,通过将问题转化为图的最短路问题来求解。对于每个区间 `[x, y]` 要求至少有 `z` 个整数,可转化为不等式 `s[y] - s[x - 1] >= z`,其中 `s[i]` 表示从 `1` 到 `i` 选择的整数数量。同时,还有隐含条件 `0 <= s[i] - s[i - 1] <= 1`,表示每个位置最多选一个整数,最少不选。 ### 解题思路 1. **构建图**:将不等式转化为图的边。对于 `s[y] - s[x - 1] >= z`,可变形为 `s[x - 1] <= s[y] - z`,对应图中从 `y` 到 `x - 1` 有一条权值为 `-z` 的边;对于 `s[i] - s[i - 1] >= 0` 变形为 `s[i - 1] <= s[i]`,对应图中从 `i` 到 `i - 1` 有一条权值为 `0` 的边;对于 `s[i] - s[i - 1] <= 1` 变形为 `s[i] <= s[i - 1] + 1`,对应图中从 `i - 1` 到 `i` 有一条权值为 `1` 的边。 2. **求解最短路**:使用最短路算法(如 SPFA 算法)求解图的最短路。从一个源点开始,不断更新各点的最短距离。 3. **得出结果**:最终 `s[最大位置]` 即为满足所有区间要求的最少整数数量。 ### 代码实现 ```cpp #include <cstdio> #include <iostream> using namespace std; const int maxn = 100010; int head[maxn], nnext[maxn], to[maxn], team[maxn], length[maxn]; int n; int tot, s = 0, t = 0; int dis[maxn]; bool b[maxn]; void add(int x, int y, int l) { tot++; nnext[tot] = head[x]; head[x] = tot; to[tot] = y; length[tot] = l; } int main() { scanf("%d", &n); for (int i = 0; i <= 1000; i++) { add(i, i - 1, -1); add(i - 1, i, 0); } for (int i = 1; i <= n; i++) { int x, y, z; cin >> x >> y >> z; add(x - 1, y, z); } for (int i = 0; i <= 1000; i++) { dis[i] = -1e9; } dis[0] = 0; team[t] = 0; t++; b[0] = true; while (s != t) { int now = team[s]; s++; b[now] = false; for (int i = head[now]; i; i = nnext[i]) { int y = to[i]; if (dis[y] < dis[now] + length[i]) { dis[y] = dis[now] + length[i]; if (!b[y]) { team[t] = y; t++; b[y] = true; } } } } cout << dis[1000]; return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值