题目链接

UOJ #7

题解

首先这一定是DP!可以写出:

\[f[i] = \min_{ancestor\ j} \{f[j] + (d[j] - d[i]) * p[i] + q[i]\}
\]

其中\(d[i]\)表示树上\(i\)的深度。

整理一下式子:

\[f[i] = \min_{ancestor\ j} \{f[j] - d[j] * p[i]\} + d[i] * p[i] + q[i]
\]

看起来可以斜率优化?

推一下式子:设\(j < k\),\(i\)从\(j\)转移优于从\(k\)转移:

\[f[j] - d[j] * p[i] < f[k] - d[k] * p[i]
\]

\[\frac{f[k] - f[j]}{d[k] - d[j]} > p[i]
\]

好的!

所以应该维护一个下凸壳,在上面二分即可。

可是由于限制条件,每个结点\(i\)对应的下凸壳都是不同的,怎么办呢?

考虑一条链的情况:每个\(f[i]\)都是可以由一个区间内的凸包得到。

可以用线段树维护当前处理完的所有点的凸包,线段树上每个节点上存储着一个凸包,查询的时候相当于在线段树上区间查询——如果当前节点所代表的区间完全包含在查询区间里面,则在这个凸包上二分查询这个区间可以带来的最优解,否则递归,就可以得到答案了。

现在再考虑把一条链上的情况推广到树上。

考虑DFS,栈中的节点组成从根到当前节点的一条链,如果线段树维护了这条链的信息,则可以像正常序列上的情况一样求当前点的\(f\)值。

如果当前点DFS完毕出栈时,可以在线段树上删除它,就可以不影响复杂度地保证时时刻刻线段树维护的都是栈中所有节点的信息,就可以求出答案了。

于是引入【可撤销的凸包】,每次可以【撤销】——回到上一次插入新节点的操作之前。

怎么实现?当插入一个新节点时,二分它要放到哪个位置,并记录当前的top和被新节点取代的节点是谁,当撤销这次插入时,恢复top和被取代的节点即可。这样插入是\(O(\log n)\)的,撤销操作是\(O(1)\)的,非常科学 =v=

代码比想象中好写(雾

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <vector>
#define space putchar(' ')
#define enter putchar('\n')
using namespace std;
typedef long long ll;
template <class T>
void read(T &x){
char c;
bool op = 0;
while(c = getchar(), c < '0' || c > '9')
if(c == '-') op = 1;
x = c - '0';
while(c = getchar(), c >= '0' && c <= '9')
x = x * 10 + c - '0';
if(op) x = -x;
}
template <class T>
void write(T x){
if(x < 0) putchar('-'), x = -x;
if(x >= 10) write(x / 10);
putchar('0' + x % 10);
} const int N = 200005;
int n, T, adj[N], nxt[N], fa[N], d_top, id[N];
ll f[N], d[N], p[N], q[N], w[N], lim[N], d_stk[N];
int seg_dep[4*N], pre[N][20], last_top[N][20], top[4*N];
vector <int> stk[4*N];
typedef long double ld;
//pre[i][j]是i号节点在线段树上第j层加入所属的凸包中时, "取代"的点的编号, last[i][j]是加入前该凸包的大小
void build(int k, int l, int r){
seg_dep[k] = seg_dep[k >> 1] + 1;
stk[k].resize(r - l + 3);
if(l == r) return;
int mid = (l + r) >> 1;
build(k << 1, l, mid);
build(k << 1 | 1, mid + 1, r);
}
bool cmp1(int i, int j, int k){ // 判断(i, j, k)是否构成上凸
return (ld)(d[j] - d[i]) * (f[k] - f[j]) < (ld)(f[j] - f[i]) * (d[k] - d[j]);
}
int find_pos(int k, int i){ // 在k号单调栈中插入i号点, 应该放在哪个位置
if(!top[k]) return 1;
int l = 2, r = top[k] + 1, mid; // 找到第一个和i上凸的位置(新来的i应该取代这个位置)
while(l < r){
mid = (l + r) >> 1;
if(cmp1(stk[k][mid - 1], stk[k][mid], i)) r = mid;
else l = mid + 1;
}
return l;
}
void push(int k, int i){
int p = find_pos(k, i);
last_top[i][seg_dep[k]] = top[k];
pre[i][seg_dep[k]] = stk[k][p];
top[k] = p;
stk[k][p] = i;
}
void rollback(int k){
int i = stk[k][top[k]];
stk[k][top[k]] = pre[i][seg_dep[k]];
top[k] = last_top[i][seg_dep[k]];
}
ll calc(int u, int v){
return f[u] + (d[v] - d[u]) * p[v] + q[v];
}
bool cmp2(int i, int j, ll x){ // 判断i和j构成的斜率是否小于等于x
return f[j] - f[i] <= (ld) x * (d[j] - d[i]);
}
ll ask(int k, int x){
int l = 1, r = top[k];
while(l < r){
int mid = (l + r) >> 1;
if(cmp2(stk[k][mid], stk[k][mid + 1], p[x])) l = mid + 1;
else r = mid;
}
return calc(stk[k][l], x);
}
void insert(int k, int l, int r, int p, bool flag){
flag ? push(k, id[p]) : rollback(k);
if(l == r) return;
int mid = (l + r) >> 1;
if(p <= mid) insert(k << 1, l, mid, p, flag);
else insert(k << 1 | 1, mid + 1, r, p, flag);
}
ll query(int k, int l, int r, int ql, int qr){
if(ql <= l && qr >= r) return ask(k, id[qr + 1]);
int mid = (l + r) >> 1;
ll ret = 9e18;
if(ql <= mid) ret = query(k << 1, l, mid, ql, qr);
if(qr > mid) ret = min(ret, query(k << 1 | 1, mid + 1, r, ql, qr));
return ret;
}
void dfs(int u){
d_stk[++d_top] = d[u] = d[fa[u]] + w[u], id[d_top] = u;
if(u != 1){
int st = lower_bound(d_stk + 1, d_stk + d_top + 1, d[u] - lim[u]) - d_stk;
f[u] = query(1, 1, n, st, d_top - 1);
}
insert(1, 1, n, d_top, 1);
for(int v = adj[u]; v; v = nxt[v]) dfs(v);
insert(1, 1, n, d_top, 0);
d_top--;
} int main(){
read(n), read(T);
build(1, 1, n);
for(int i = 2; i <= n; i++){
read(fa[i]), read(w[i]), read(p[i]), read(q[i]), read(lim[i]);
nxt[i] = adj[fa[i]], adj[fa[i]] = i;
}
dfs(1);
for(int i = 2; i <= n; i++)
write(f[i]), enter;
return 0;
}

UOJ#7. 【NOI2014】购票 | 线段树 凸包优化DP的更多相关文章

  1. UOJ#7 NOI2014 购票 点分治+凸包二分 斜率优化DP

    [NOI2014]购票 链接:http://uoj.ac/problem/7 因为太麻烦了,而且暴露了我很多学习不扎实的问题,所以记录一下具体做法. 主要算法:点分治+凸包优化斜率DP. 因为$q_i ...

  2. LOJ #2537. 「PKUWC 2018」Minimax (线段树合并 优化dp)

    题意 小 \(C\) 有一棵 \(n\) 个结点的有根树,根是 \(1\) 号结点,且每个结点最多有两个子结点. 定义结点 \(x\) 的权值为: 1.若 \(x\) 没有子结点,那么它的权值会在输入 ...

  3. BZOJ 3672[NOI2014]购票(树链剖分+线段树维护凸包+斜率优化) + BZOJ 2402 陶陶的难题II (树链剖分+线段树维护凸包+分数规划+斜率优化)

    前言 刚开始看着两道题感觉头皮发麻,后来看看题解,发现挺好理解,只是代码有点长. BZOJ 3672[NOI2014]购票 中文题面,题意略: BZOJ 3672[NOI2014]购票 设f(i)f( ...

  4. BZOJ_3672_ [Noi2014]购票_CDQ分治+斜率优化

    BZOJ_3672_ [Noi2014]购票_CDQ分治+斜率优化 Description  今年夏天,NOI在SZ市迎来了她30周岁的生日.来自全国 n 个城市的OIer们都会从各地出发,到SZ市参 ...

  5. [BZOJ3672][UOJ#7][NOI2014]购票

    [BZOJ3672][UOJ#7][NOI2014]购票 试题描述  今年夏天,NOI在SZ市迎来了她30周岁的生日.来自全国 n 个城市的OIer们都会从各地出发,到SZ市参加这次盛会.       ...

  6. Codeforces Round #278 (Div. 1) Strip (线段树 二分 RMQ DP)

    Strip time limit per test 1 second memory limit per test 256 megabytes input standard input output s ...

  7. BZOJ 3672: [Noi2014]购票( 树链剖分 + 线段树 + 凸包 )

    s弄成前缀和(到根), dp(i) = min(dp(j) + (s(i)-s(j))*p(i)+q(i)). 链的情况大家都会做...就是用栈维护个下凸包, 插入时暴力弹栈, 查询时就在凸包上二分/ ...

  8. UOJ 7 NOI2014 购票

    题意:给一棵树计算一下各个点在距离限制下以一定的费用公式通过不停地到祖先最后到达一号点的最小花费. 第一种做法:线段树维护带修凸壳.显然的,这个公式计算是p*x+q 所以肯定和斜率有关系.然后这题的d ...

  9. 【BZOJ2402】陶陶的难题II 分数规划+树链剖分+线段树+凸包

    题解: 首先分数规划是很明显的 然后在于我们如何要快速要求yi-mid*xi的最值 这个是看了题解之后才知道的 这个是斜率的一个基本方法 我们设y=mid*x+z 那么显然我们可以把(x,y)插入到一 ...

随机推荐

  1. 收藏pdf 链接

    python 入门: https://files.cnblogs.com/files/minsons/python%E4%BB%8E%E5%85%A5%E9%97%A8%E5%88%B0%E6%B7% ...

  2. Visual Studio2017 数据库架构比较

    一.前言 开发的时候在测试服务器上和线网服务器上面都有我们的数据库,当我们在线网上面修改或者新增一些字段后,线网的数据库也需要更新,这个时候根据表的修改记录,然后在线网上面一个一个增加修改很浪费效率而 ...

  3. 身在上海的她,该不该继续"坚持"前端开发?

    作者:13 GitHub:https://github.com/ZHENFENG13 版权声明:本文为原创文章,未经允许不得转载. 一 对于目前的IT行业,我实在不想她还没在这个行业中站稳脚跟就开始有 ...

  4. Redis未授权访问漏洞的利用及防护

    Redis未授权访问漏洞的利用及防护 什么是Redis未授权访问漏洞? Redis在默认情况下,会绑定在0.0.0.0:6379.如果没有采取相关的安全策略,比如添加防火墙规则.避免其他非信任来源IP ...

  5. linux下安装redis组件报错-gcc报错

    报错如图: 1.解决办法    先安装gcc插件.删除redis解压后文件.重新解压

  6. Nginx code 常用状态码学习小结

    最近了解下Nginx的Code状态码,在此简单总结下.一个http请求处理流程: 一个普通的http请求处理流程,如上图所示:A -> client端发起请求给nginxB -> ngin ...

  7. Gitlab环境快速部署(RPM包方式安装)

    之前梳理了一篇Gitlab的安装CI持续集成系统环境---部署Gitlab环境完整记录,但是这是bitnami一键安装的,版本比较老.下面介绍使用rpm包安装Gitlab,下载地址:https://m ...

  8. WinForm多线程+委托防止界面假死

    当有大量数据需要计算.显示在界面或者调用sleep函数时,容易导致界面卡死,可以采用多线程加委托的方法解决 using System; using System.Collections.Generic ...

  9. dxteam团队项目终审报告

    一. 团队成员的简介和个人博客地址 M1阶段 http://www.cnblogs.com/dxteam/p/3991514.html M2阶段 新成员 邓亚梅 http://www.cnblogs. ...

  10. (转)SqlDateTime 溢出。必须介于 1/1/1753 12:00:00 AM 和 12/31/9999 11:59:59 PM之间

    原因: 出现这种问题多半是因为你插入或者更新数据库时,datetime字段值为空默认插入0001年01月01日造成datetime类型溢出. 传给数据库表的时间类型值是null值.这里的null指的是 ...