传送门


\(DP\)

设\(f_i\)表示第\(i\)个节点的答案,\(S_i\)表示\(i\)的子节点集合,那么转移方程为\(f_i = \min\limits_{j \in S_i} \{a_i \times b_j + f_j\}\)

这是一个很明显的斜率优化式子,斜率为\(b_j\),截距为\(f_j\),自变量为\(a_i\)。考虑到斜率没有单调性,所以使用set维护凸包。

使用set维护凸包比较简单。一条直线插入时,先判断这条线段在当前凸包中是否合法,然后不断把两边不合法的直线删去。具体的实现看下面代码的insert函数吧

然后如何将一个点所有儿子的set合并为自己的set呢?使用启发式合并来保证复杂度。

最后,知道了当前点的所有儿子节点的凸包,如何计算当前点的答案?正解是二分斜率,因为你没法在set上直接二分直线。

总复杂度为\(O(nlog^2n)\)

还可以通过dfn序将树上问题转化为序列问题,对一个点有贡献的是一段连续区间,就可以用CDQ分治解决。

#include<bits/stdc++.h>
#define int long long
#define ld long double
//This code is written by Itst
using namespace std; inline int read(){
int a = 0;
bool f = 0;
char c = getchar();
while(c != EOF && !isdigit(c)){
if(c == '-')
f = 1;
c = getchar();
}
while(c != EOF && isdigit(c)){
a = (a << 3) + (a << 1) + (c ^ '0');
c = getchar();
}
return f ? -a : a;
} const int MAXN = 100010;
struct Edge{
int end , upEd;
}Ed[MAXN << 1];
struct node{
int k , b;
}now;
set < node > s[MAXN];
long long sum[MAXN] , a[MAXN] , b[MAXN] , minN[MAXN] , fa[MAXN] , size[MAXN] , head[MAXN] , N , cntEd; bool operator <(node a , node b){
return a.k < b.k || (a.k == b.k && a.b < b.b);
} inline void addEd(int a , int b){
Ed[++cntEd].end = b;
Ed[cntEd].upEd = head[a];
head[a] = cntEd;
} void dfs(int now , int f){
fa[now] = f;
size[now] = 1;
for(int i = head[now] ; i ; i = Ed[i].upEd)
if(Ed[i].end != f){
dfs(Ed[i].end , now);
size[now] += size[Ed[i].end];
}
if(size[now] == 1)
minN[now] = 0;
} inline ld calcNode(node a , node b){
return (a.b - b.b) / (ld)(b.k - a.k);
} inline void insert(int now , int k , int b){//插入一条斜率为k、截距为b的直线
node x , l , r;
x.k = k;
x.b = b;
set < node > :: iterator it = s[now].lower_bound(x);
if(it != s[now].end() && (*it).k == k){//判断是否存在斜率相同的直线
s[now].erase(it);
it = s[now].lower_bound(x);
}
else
if(it != s[now].begin() && (*--it).k == k)
return;
it = s[now].lower_bound(x);
if(it != s[now].begin() && it != s[now].end()){
l = *it , r = *(--it);
if(calcNode(r , x) < calcNode(l , r) && calcNode(l , r) < calcNode(l , x))//判断这一条直线是否能够被加入当前凸包
return;
++it;
}
while(1){//向两边删去不合法直线
it = s[now].lower_bound(x);
if(it == s[now].end())
break;
l = *it;
if(++it == s[now].end())
break;
r = *it;
if(calcNode(l , r) > calcNode(l , x) && calcNode(l , r) > calcNode(r , x))
s[now].erase(--it);
else
break;
}
while(1){
it = s[now].lower_bound(x);
if(it == s[now].begin())
break;
l = *(--it);
if(it == s[now].begin())
break;
r = *(--it);
if(calcNode(l , r) < calcNode(l , x) && calcNode(l , r) < calcNode(r , x))
s[now].erase(++it);
else
break;
}
s[now].insert(x);
} inline void merge(int root , int now){
bool f = 0;
if(size[root] < size[now]){
swap(root , now);
f = 1;
}
for(set < node > :: iterator it = s[now].begin() ; it != s[now].end() ; ++it)
insert(root , (*it).k , (*it).b);
if(f){
s[now] = s[root];
swap(now , root);
}
s[now].clear();
} void dsu(int now){//别被误导了,这个不是dsu on tree
if(size[now] == 1){
insert(now , b[now] , minN[now]);
return;
}
for(int i = head[now] ; i ; i = Ed[i].upEd)
if(Ed[i].end != fa[now]){
dsu(Ed[i].end);
merge(now , Ed[i].end);
}
int l = -1e5 - 1 , r = 1e5 + 1;
set < node > :: iterator it;
node L , R;
while(l < r){
int mid = l + r >> 1;
it = s[now].lower_bound((node){mid , (long long)-1e15});
if(it == s[now].begin()){
l = mid + 1;
continue;
}
if(it == s[now].end()){
r = mid;
continue;
}
L = *it;
R = *(--it);
if(R.k * a[now] + R.b >= L.k * a[now] + L.b)
l = mid + 1;
else
r = mid;
}
L = *s[now].lower_bound((node){l - 1 , (long long)-1e15});
minN[now] = L.k * a[now] + L.b;
insert(now , b[now] , minN[now]);
} signed main(){
N = read();
memset(minN , 0x3f , sizeof(minN));
for(int i = 1 ; i <= N ; i++)
a[i] = read();
for(int i = 1 ; i <= N ; i++)
b[i] = read();
for(int i = 1 ; i < N ; i++){
int a = read() , b = read();
addEd(a , b);
addEd(b , a);
}
dfs(1 , 0);
dsu(1);
for(int i = 1 ; i <= N ; i++)
cout << minN[i] << ' ';
return 0;
}

CF932F Escape Through Leaf 斜率优化、启发式合并的更多相关文章

  1. CF932F Escape Through Leaf

    CF932F Escape Through Leaf 首先, $ O(n^2) $ dp 是很显然的,方程长这样: \[dp[u] = min\{dp[v] + a_u\times b_v\} \] ...

  2. CF932F Escape Through Leaf(DP,斜率优化)

    SB 题. 写出 DP 方程:\(f_i\) 表示从 \(i\) 跳的最小值. \(i\) 是叶子就是 \(0\),否则就是选个子树中的 \(v\),\(f_i=\min(f_v+a_ib_v)\). ...

  3. Codeforces Round #463 F. Escape Through Leaf (李超线段树合并)

    听说正解是啥 set启发式合并+维护凸包+二分 根本不会啊 , 只会 李超线段树合并 啦 ... 题意 给你一颗有 \(n\) 个点的树 , 每个节点有两个权值 \(a_i, b_i\) . 从 \( ...

  4. 【CF932F】Escape Through Leaf 启发式合并set维护凸包

    [CF932F]Escape Through Leaf 题意:给你一棵n个点的树,每个点有树形ai和bi,如果x是y的祖先,则你可以从x花费$a_x\times b_y$的费用走到y(费用可以为负). ...

  5. [多校 NOIP 联合模拟 20201130 T4] ZZH 的旅行(斜率优化dp,启发式合并,平衡树)

    题面 题目背景 因为出题人天天被 ZZH(Zou ZHen) 吊打,所以这场比赛的题目中出现了 ZZH . 简要题面 数据范围 题解 (笔者写两个log的平衡树和启发式合并卡过的,不足为奇) 首先,很 ...

  6. Codeforces 932.F Escape Through Leaf

    F. Escape Through Leaf time limit per test 3 seconds memory limit per test 256 megabytes input stand ...

  7. 【CF 463F】Escape Through Leaf

    题意 给你一棵 \(n\) 个点的树,每个节点有两个权值 \(a_i,b_i\). 从一个点 \(u\) 可以跳到以其为根的子树内的任意一点 \(v\)(不能跳到 \(u\) 自己),代价是 \(a_ ...

  8. @codeforces - 932F@ Escape Through Leaf

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 给定一个 n 个点的树(标号1~n),以结点 1 为根.每个结点 ...

  9. [bzoj3123][sdoi2013森林] (树上主席树+lca+并查集启发式合并+暴力重构森林)

    Description Input 第一行包含一个正整数testcase,表示当前测试数据的测试点编号.保证1≤testcase≤20. 第二行包含三个整数N,M,T,分别表示节点数.初始边数.操作数 ...

随机推荐

  1. 消除2个按钮之间1px细节引起的冲突

    1.代码 <!doctype html> <html lang="en"> <head> <meta charset="UTF- ...

  2. loadrunner 脚本开发-基本知识

    脚本开发-基本知识 1)编码工具设置 自动补全输入Tools->General Options->Environment->Auto complete word 显示功能语法Tool ...

  3. Kotlin入门(3)基本变量类型的用法

    上一篇文章介绍了Kotlin在App开发中的简单用法,包括操纵控件对象.设置控件监听器,以及弹出Toast提示等等.也许大家已经迫不及待想要了解更深入的App开发,可是由于Kotlin是一门全新的语言 ...

  4. centos7 Linux 安装jdk1.8

    在CentOS7上安装JDK1.8 1 通过 xshell 连接到CentOS7 服务器: 2 进入到目录 /usr/local/ 中(一般装应用环境我们都会在这个目录下装,也可自行选择目录): cd ...

  5. 【js基础】创建对象的几种常见模式(工厂模式,构造函数模式,原型模式,构造原型组合模式)

    一.工厂模式 缺点:没有解决对象识别的问题 优点:解决了创建多个相似对象的问题 function createPerson(name,age,job){ var o = new Object(); o ...

  6. datagridview 行高列宽的自动设置

    1) 设定行高和列宽自动调整 [C#]// 设定包括Header和所有单元格的列宽自动调整 DataGridView1.AutoSizeColumnsMode = DataGridViewAutoSi ...

  7. JS代码段:VUE下的时间,星期和年月日

    不为别的,只为以后复制粘贴方便 data() { return { date: "", time: "", week: "" }; }, / ...

  8. sftp 建立用户

    1.创建sftp组:#groupadd sftp 2.创建测试账户:#useradd -g sftp -s /bin/false testuser 修改密码:# passwd sftp 3.修改测试账 ...

  9. 【PAT】B1077 互评成绩计算(20 分)

    录入成绩,直接将所有同学给的分数相加,排序,减去最大和最小 省去了遍历一次 注意四舍五入 #include<cstdio> #include<string.h> #includ ...

  10. Hive-1.2.1_01_安装部署

    前言:该文章是基于 Hadoop2.7.6_01_部署 进行的. 1. Hive基本概念 1.1. 什么是Hive Hive是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张数据库 ...