首页 > 其他分享 >APIO2016 烟火表演

APIO2016 烟火表演

时间:2024-09-17 20:24:02浏览次数:1  
标签:rt 结点 int 拐点 APIO2016 表演 斜率 烟火 len

传送门

给定一棵树,带边权。\(1\) 的代价可以使某边权 \(\pm 1\)。求最小代价使从根到叶子距离都相等。
\(n\le 3\times 10^5,w_e\le 10^9\)。


\(f_u(x)\) 表示 \(u\) 的子树内把 \(u\) 到叶子的距离都变成 \(x\) 的最小代价。\(F_u(x)\) 表示 \(u\) 的子树内把 \(fa[u]\) 到叶子的距离都变成 \(x\) 的最小代价。

写出转移方程。

\[f_v(x)=\sum_{c\in son(v)}F_c(x) \]

\[F_c(x)=\min_{d}\{|len-d|+_c(x-d)\} \]

其中 \(len\) 表示 \(c\) 和 \(fa[c]\) 的边长度。

引理:\(f_v()\) 是下凸函数,\(F_c()\) 是下凸函数。

证明:

当 \(F_c\) 是下凸的,试证明 \(f_{fa[c]}\) 下凸。
\(f_{fa[c]}()=\sum F_c()\)。考虑二阶导数,\(F_c\) 的二阶导为正,则 \(f_{fa[c]}()\) 的二阶导等于 \(F_c()\) 二阶导求和,也是正的。所以 \(f_{fa[c]}()\) 是下凸的。

当 \(f_c\) 是下凸的,试证明 \(F_c\) 下凸。
记 \(g(d)=|d-len|\)。

\[F_c(x)=\min\{g(0)+f_c(x),g(1)+f_c(x-1),\dots\} \]

我们把这些图像都画一下。

观察到 \(f(x-1)\) 相较于 \(f(x)\) 右移了一个单位长度;\(g(1)\) 相较于 \(g(0)\) (如果 \(len\ge 1\) 的话)向下移了一个单位长度。

所以 \(g(1)+f(x-1)\) 比 \(g(0)+f(x)\) 向右下方移了 \(1\) 格(如果 \(len\ge 1\));容易想到,当 \(d>len\) 之后,\(g(d)+f(x-d)\) 比 \(g(d-1)+f(x-d+1)\) 向右上方移了 \(1\) 格。

当取了 \(\min\) 之后(\(F_c\))是什么样的?

保留 \(g(0)+f(x)\) 最低点及其左侧的拐点 \(x_0\),然后从 \(x_0\sim x_0+len\) 都是一条斜率 \(-1\) 的直线,\(x_0+len\sim +\infty\) 都是一条斜率 \(+1\) 的直线。

显然这还是一个下凸函数。(其实这个证明很不严谨,感性理解吧)


基于证明,我们尝试维护 \(f_c\) 和 \(F_c\) 的图像。因为是凸的,维护拐点以及最左侧的起始点即可。

第一个问题是用什么维护拐点。用一个可重集维护,每一个元素都记录一个拐点;当一个元素重复出现,比如出现了 \(x\) 次,表示这个拐点到上一个拐点的斜率,相比于上一个拐点到上上个拐点的斜率,增加了 \(x\)。

最左侧的起始点,就是 \((0,f_c(0))\) 或者 \((0,F_c(0))\),容易求。

第二个问题是 \(F_{son}\rightarrow f_{fa}\) 时,拐点是如何变化的。
容易想到,只要把所有 \(son\) 的 \(F\) 图像拐点,全部合并(merge)起来即可。注意到从 \(f_c\) 最后一个拐点到 \(+\infty\) 的斜率是 \(c\) 的儿子数。

第三个问题是 \(f_c\rightarrow F_c\) 时,拐点是如何变化的。
观察上面证明的图像变化。

  1. 把斜率 \(>0\) 的全部改成斜率 \(1\),也就是把斜率 \(>0\) 的拐点全部 pop 了。
  2. 把斜率 \(=0\) 的那一段,向右平移 \(len\) 格。在做完 \(1\) 后,最靠右的两个拐点一定是斜率 \(0\) 的那一段。把它们取出来,都 \(+len\) 再放回去即可。
  3. "斜率 \(=0\) 的那一段的左端点" 到 "它左侧的拐点" 的斜率改成 \(-1\)。其实在做 \(2\) 的时候就已经顺便做了。

到这里已经可以做了。但是我们可以再多观察一个性质,更加优美地写代码。
因为每个 \(F\) 相邻拐点的斜率都 \(\le 0\),所以当产生斜率 \(\ge 1\) 的直线,必然是两个 \(F\) 合并了,而且必然产生且只产生一个。
所以 \(f_c\rightarrow F_c\) 进行 1 的时候,要把斜率 \(>0\) 的拐点全部 pop 了。这种拐点的个数就是 \(c\) 的儿子个数 \(-1\)。

维护拐点的集合,因为只会从右边删,用左偏树即可。

注意到我们其实并不需要同时维护 \(f\) 和 \(F\) 的图像。对于一个结点,它的 \(f\) 只在更新 \(F\) 的时候起作用,\(F\) 只在更新 \(f\) 的时候起作用,所以我们只维护 \(F\) 的图像。拿一个结点的 \(F\) 更新父结点的 \(f\),然后舍弃这个结点的 \(F\)。等到处理父结点的时候,再把 \(f\) 变成 \(F\)。注意在处理 \(1\) 的时候,不要把 \(f_1\) 也弄成 \(F_1\) 了。

当我们拥有了 \(f_1\),找到 \(f_1\) 的图像最低点。其函数值就是答案。

但是这里还有一个方法,可以简化我们求函数值的过程。首先 \(f_1(0)\) 就是所有边权的和,非常简单。\(f_1(0)\) 减去 "斜率 \(=0\) 的那一段的左端点及它左侧的所有拐点" 的 \(x\) 坐标之和,就是最低点的函数值。

注意到拐点总个数是 \(O(n)\) 的,所以复杂度 \(O(n\log n)\)。

点击查看代码
#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int N = 6e5 + 5;

struct Node {
	ll val;
	int l, r, d;
	
	Node() {
		val = l = r = 0;
		d = -1;
	}
	Node(ll v) {
		val = v;
		l = r = d = 0;
	}
};
int sz = 0;
Node a[N];
int new_Node(ll v) { //新建一个权值为v的结点 
	a[++sz] = Node(v);
	return sz;
}
int mrg(int L, int R) { //合并以L,R为根的左偏树,返回最终根结点 
	if (L == 0 || R == 0)
		return L + R;
	if (a[L].val < a[R].val)
		swap(L, R);
	a[L].r = mrg(a[L].r, R);
	if (a[a[L].l].d < a[a[L].r].d)
		swap(a[L].l, a[L].r);
	a[L].d = a[a[L].r].d + 1;
	return L;
}
int pop(int x) { //弹出x,顺便返回新堆的根结点 
	return mrg(a[x].l, a[x].r);
}

int rt[N]; //rt[i]记录结点i的左偏树的根 

int n, m;
int p[N], c[N]; //父结点,到父结点的边权 
int sons[N] = {}; //儿子个数 

int main() {
	cin >> n >> m;
	ll f10 = 0;
	for (int i = 2; i <= n + m; i++) {
		cin >> p[i] >> c[i];
		f10 += c[i];
		sons[p[i]]++;
	}
	
	for (int i = n + m; i >= 2; i--) {
		for (int j = 1; j < sons[i]; j++) //第一步:弹出所有斜率>0的 
			rt[i] = pop(rt[i]);
		//第二步:把斜率0的那一段向右平移len(c[i])
		ll R = a[rt[i]].val;
		rt[i] = pop(rt[i]);
		ll L = a[rt[i]].val;
		rt[i] = pop(rt[i]);
		rt[i] = mrg(rt[i], mrg(new_Node(L + c[i]), new_Node(R + c[i])));
		//第三步:右端改成斜率1的,已经完成
		
		//当前结点的F合并到父结点的f
		rt[p[i]] = mrg(rt[p[i]], rt[i]);
	}
	//把f1的斜率>0的也pop了,这样堆顶就是最低点
	for (int j = 1; j < sons[1]; j++)
		rt[1] = pop(rt[1]);
	rt[1] = pop(rt[1]); //把slope=0的右端点pop了
	while (rt[1]) {
		f10 -= a[rt[1]].val;
		rt[1] = pop(rt[1]);
	}
	cout << f10 << endl;
	return 0;
}

标签:rt,结点,int,拐点,APIO2016,表演,斜率,烟火,len
From: https://www.cnblogs.com/FLY-lai/p/18417000

相关文章

  • 火焰检测算法、明烟明火检测、烟火检测算法
    烟火检测算法主要用于火灾早期预警系统中,能够在火灾初期阶段及时发现烟雾或火焰,从而快速响应并采取行动,以减少火灾带来的损失。以下是对烟火检测算法的应用场景及优势的详细介绍。烟火检测算法广泛应用于多种场景中,以下是一些典型的应用实例:1.公共安全监控-楼宇监控:在办公楼、......
  • 奇瑞车模表演引众怒,低俗营销何时休?上市之路蒙阴影
    在最近的2024成都车展上,奇瑞汽车再次被推上了舆论的风口浪尖。这次,不是因为其车型的创新或技术的突破,而是因为车模的争议性表演。据现场视频显示,奇瑞iCAR展位的一名车模在车展上作出了大尺度的动作,引发了大量围观和拍照,其中不乏儿童的身影。这一幕迅速在网络上引发了广泛争议,不......
  • 秸秆禁烧烟火识别系统
    秸秆禁烧烟火识别系统一旦检测到烟雾,秸秆禁烧识别系统将自动监测监控画面中是否存在秸秆焚烧处理,不用人工干涉。当秸秆禁烧烟火识别系统监测到火苗时,系统会自动报警,通知监控管理中心,提示相关人员及时处理。与此同时,将警报截屏和视频保存到数据表中,自动汇总。秸秆禁烧识别系统在......
  • 智能烟火识别预警软件 CNN
    智能烟火识别预警软件采用人工智能技术,智能烟火识别预警软件在工厂、工地等场所利用已经安装的摄像头,智能烟火识别预警软件对场内的烟花爆竹进行实时监测。当场内出现烟花爆竹时,智能烟火识别预警软件将自动发出警报,并通过人工智能算法通知现场管理人员进行处理。智能烟火识别预警软......
  • 从表演基础到LLM Roleplay的动作描写
    最近在阅读表演基础的教程时,发现其实是可以迁移到角色扮演的描述上的形体语言什么是形体语言?这是我们首先要解决的问题.人类常常不会不自觉的运用身体部位,并通过这些部位的姿态来表达情感.常见的部位有:面部:皱眉手部:握拳,摊开,合拢,手指肩部:用肩部轻触,耸......
  • 从表演基础到LLM Roleplay的动作描写
    最近在阅读表演基础的教程时,发现其实是可以迁移到角色扮演的描述上的形体语言什么是形体语言?这是我们首先要解决的问题.人类常常不会不自觉的运用身体部位,并通过这些部位的姿态来表达情感.常见的部位有:面部:皱眉手部:握拳,摊开,合拢,手指肩部:用肩部轻触,耸......
  • MySQL 情节:SQL 语句的表演
    本文由ChatMoney团队出品第一幕:解析与优化-“翻译官与谋士”SQL解析器是第一个上场的角色,任务就是把SQL请求翻译成MySQL能听懂的语言。就像你点餐时,服务员得听懂你到底要什么菜。不然你说“我要一盘炒青菜”,结果服务员听成了“我要一盘草皮”,那谁也吃不下去啊!接下来......
  • AI烟火识别算法在消防安全与火灾预警系统中的应用与价值
    在信息化和智能化的今天,烟火识别算法作为一种重要的技术工具,在火灾预防和处理中发挥着关键作用。其工作原理主要基于深度学习和图像处理技术,能够实时分析监控画面,准确检测出图像中的烟火,并发出预警。一、烟火识别算法的工作原理烟火识别算法的工作原理主要基于深度学习和图像处......
  • 比亚迪一4S店着火:浅述烟火识别技术与消防安全预警方案的必要性
    据新闻报道,2024年5月16日,福建福州一家比亚迪4S店发生火灾。事发后,当地消防立即调员赶往现场救援,大火导致展厅展车基本烧毁,部分维修车辆受损,没有人员伤亡。随着汽车市场的不断扩大,4S店作为汽车销售和售后服务的重要场所,其消防安全问题日益受到关注。传统的消防监控方式已不能满足......
  • AI智能分析高精度烟火算法EasyCVR视频方案助力打造森林防火建设
    一、背景随着夏季的来临,高温、干燥的天气条件使得火灾隐患显著增加,特别是对于广袤的森林地区来说,一旦发生火灾,后果将不堪设想。在这样的背景下,视频汇聚系统EasyCVR视频融合云平台+AI智能分析在森林防火中发挥着至关重要的作用。二、方案描述视频汇聚EasyCVR视频融合云平台是一......