题目描述

今年夏天,NOI在SZ市迎来了她30周岁的生日。来自全国 n 个城市的OIer们都会从各地出发,到SZ市参加这次盛会。

全国的城市构成了一棵以SZ市为根的有根树,每个城市与它的父亲用道路连接。为了方便起见,我们将全国的 n 个城市用 1 到 n 的整数编号。其中SZ市的编号为 1。对于除SZ市之外的任意一个城市 v,我们给出了它在这棵树上的父亲城市 fv 以及到父亲城市道路的长度 sv。

从城市 v 前往SZ市的方法为:选择城市 v 的一个祖先 a,支付购票的费用,乘坐交通工具到达 a。再选择城市 a 的一个祖先 b,支付费用并到达 b。以此类推,直至到达SZ市。

对于任意一个城市 v,我们会给出一个交通工具的距离限制 lv。对于城市 v 的祖先 a,只有当它们之间所有道路的总长度不超过 lv 时,从城市 v 才可以通过一次购票到达城市 a,否则不能通过一次购票到达。对于每个城市 v,我们还会给出两个非负整数 pv,qv 作为票价参数。若城市 v 到城市 a 所有道路的总长度为 d,那么从城市 v 到城市 a 购买的票价为 dpv+qv。

每个城市的OIer都希望自己到达SZ市时,用于购票的总资金最少。你的任务就是,告诉每个城市的OIer他们所花的最少资金是多少。

题解

观察每个节点的答案表达式

dp[i]=min(dp[j]+(dis[i]-dis[j])*p[i]+q[i])

一个斜率一个常数,显然需要斜率优化。

先考虑树是一条链的情况

设i到根节点的两个祖先为j和k,且deep[k]>deep[j]

那么如果要保留j的话,要满足val(j)<val(k)

dp[j]+dis[i]*p[i]-dis[j]*p[i]+q[i]<dp[k]+dis[i]*p[i]-dis[k]*p[i]+q[i]。

dp[j]-dis[j]*p[i]<dp[k]-dis[k]*p[i]

(dp[k]-dp[j])/(dis[k]-dis[j])>=p[i]

这里dis是单调递增的,但p没有这种性质。

所以我们可以开一个单调栈,然后每次查询在栈内二分。

每次二份出来这个最优点就好了,插入一个数也一样,就是二分这个位置,把后面的都弹出。

然后这条链变成了一棵树。

可以先dfs这个树,然后对于一条链去维护这个栈。

我们虽然在dfs树,但是在计算机内部实现是一个栈在反复压栈弹栈。

所以我们需要支持维护这个栈,然后还需要支持撤销操作。

实现的话就维护一个以deep为下标的线段树,区间l-r的节点代表的是deep在l-r的范围内的所有点组成的单调栈,因为同一时刻合法的点深度各不相同。

所以我们要开O(n)单调栈,也可以提前开好,放在一个数组里。

每次dfs到一个节点时,就在线段树上找到包含自己的所有区间,然后对这些区间进行“加入一个数”这个操作,同时记录被改掉的信息,方便撤销。

对于一个查询,我们可以用树上倍增找出合法的深度区间,然后在线段树上查这个区间的最优解就好了,复杂度nlog2n

细节:把撤销写挂了,调了好久。

代码

#include<iostream>
#include<cstdio>
#define N 200009
#define int ll
using namespace std;
typedef long long ll;
ll n,q[N*],dis[N],dp[N],qi[N],pi[N],lim[N];
int tot,head[N],deep[N],remt[N][],remv[N][],p[N][],now=;
int ddf;
inline ll rd(){
ll x=;char c=getchar();bool f=;
while(!isdigit(c)){if(c=='-')f=;c=getchar();}
while(isdigit(c)){x=(x<<)+(x<<)+(c^);c=getchar();}
return f?-x:x;
}
struct SEG{int head,tail;}tr[N<<];
struct edge{int n,to,l;}e[N];
inline void add(int u,int v,int l){e[++tot].n=head[u];e[tot].to=v;head[u]=tot;e[tot].l=l;}
inline double get_k(int j,int k){return (double)((double)dp[j]-dp[k])/((double)dis[j]-dis[k]);}
inline int get_ans(int cnt,double p){
int l=tr[cnt].head,r=tr[cnt].tail-,ans=-;
if(tr[cnt].head>=tr[cnt].tail)return q[tr[cnt].tail];
while(l<=r){
int mid=(l+r)>>;
if(get_k(q[mid],q[mid+])>=p){ans=mid;r=mid-;}else l=mid+;
}
if(ans<)ans=tr[cnt].tail;
return q[ans];
}
int query(int cnt,int l,int r,int L,int R,double p){
if(l>=L&&r<=R)return get_ans(cnt,p);
int mid=(l+r)>>;
if(mid>=L&&mid<R){
int x=query(cnt<<,l,mid,L,R,p),y=query(cnt<<|,mid+,r,L,R,p);
if(get_k(x,y)>=p)return x;else return y;
}
else if(mid>=L)return query(cnt<<,l,mid,L,R,p);
else return query(cnt<<|,mid+,r,L,R,p);
}
inline int findqueue(int cnt,int u){
if(tr[cnt].head>=tr[cnt].tail)return tr[cnt].tail;
int l=tr[cnt].head,r=tr[cnt].tail-,ans=-;
while(l<=r){
int mid=(l+r)>>;
if(get_k(q[mid],q[mid+])>get_k(q[mid],u))ans=mid,r=mid-;
else l=mid+;
}
if(ans<)ans=tr[cnt].tail;
return ans;
}
void build(int cnt,int l,int r){
tr[cnt].tail=now-;tr[cnt].head=now;
now+=(r-l+)+;
if(l==r)return;int mid=(l+r)>>;
build(cnt<<,l,mid);build(cnt<<|,mid+,r);
}
void insert(int cnt,int l,int r,int u,int x,int dep){
int pos=findqueue(cnt,u)+;
remt[u][dep]=tr[cnt].tail;
remv[u][dep]=q[pos];
q[pos]=u;tr[cnt].tail=pos;
if(l==r)return;
int mid=(l+r)>>;
if(mid>=x)insert(cnt<<,l,mid,u,x,dep+);
else insert(cnt<<|,mid+,r,u,x,dep+);
}
void del(int cnt,int l,int r,int u,int x,int dep){
q[tr[cnt].tail]=remv[u][dep];
tr[cnt].tail=remt[u][dep];
if(l==r)return;
int mid=(l+r)>>;
if(mid>=x)del(cnt<<,l,mid,u,x,dep+);
else del(cnt<<|,mid+,r,u,x,dep+);
}
inline int jump(int x,int l){
int now=x;
for(int i=;i>=;--i)if(p[now][i]&&dis[x]-dis[p[now][i]]<=l)now=p[now][i];
return now;
}
void dfs(int u){
for(int i=;(<<i)<=deep[u];++i)p[u][i]=p[p[u][i-]][i-];
if(u!=){
int x=jump(u,lim[u]);
ddf=u;
int y=query(,,n,deep[x],deep[p[u][]],pi[u]);
dp[u]=dp[y]+(dis[u]-dis[y])*pi[u]+qi[u];
}
insert(,,n,u,deep[u],);
for(int i=head[u];i;i=e[i].n){
int v=e[i].to;dis[v]=dis[u]+e[i].l;
deep[v]=deep[u]+;p[v][]=u;dfs(v);
}
del(,,n,u,deep[u],);
}
signed main(){
n=rd();int t=rd();int father;int l;
for(int i=;i<=n;++i){
father=rd();l=rd();pi[i]=rd();qi[i]=rd();lim[i]=rd();
add(father,i,l);
}
build(,,n);
deep[]=;dfs();
for(int i=;i<=n;++i)printf("%lld\n",dp[i]);
return ;
}

[NOI2014]购票(斜率优化+线段树)的更多相关文章

  1. [NOI2014]购票——斜率优化+树链剖分+线段树

    建议到UOJ上去交 题解 一眼\(DP\),先把转移方程写出来 设\(dp[i]\)为从点\(i\)出发到点\(1\)的最小费用,那么存在转移 \[f[i]=min\{f[j]+(d[i]-d[j]) ...

  2. 【bzoj3672】[Noi2014]购票 斜率优化dp+CDQ分治+树的点分治

    题目描述  给出一棵以1为根的带边权有根树,对于每个根节点以外的点$v$,如果它与其某个祖先$a$的距离$d$不超过$l_v$,则可以花费$p_vd+q_v$的代价从$v$到$a$.问从每个点到1花费 ...

  3. [NOI2014]购票 --- 斜率优化 + 树形DP + 数据结构

    [NOI2014]购票 题目描述 今年夏天,NOI在SZ市迎来了她30周岁的生日. 来自全国 n 个城市的OIer们都会从各地出发,到SZ市参加这次盛会. 全国的城市构成了一棵以SZ市为根的有根树,每 ...

  4. [Noi2014]购票 斜率优化DP+可持久化凸包

    貌似网上大部分题解都是CDQ分治+点分治然后再斜率优化DP,我貌似并没有用这个方法. 这一题跟这题有点像,只不过多了一个l的限制 如果说直接跑斜率优化DP,存储整个序列的话,显然是不行的,如图所示(图 ...

  5. [BZOJ3672][Noi2014]购票 斜率优化+点分治+cdq分治

    3672: [Noi2014]购票 Time Limit: 30 Sec  Memory Limit: 512 MBSubmit: 1749  Solved: 885[Submit][Status][ ...

  6. BZOJ 3672 [NOI2014]购票 (凸优化+树剖/树分治)

    题目大意: 略 题面传送门 怎么看也是一道$duliu$题= = 先推式子,设$dp[x]$表示到达$x$点到达1节点的最小花费 设$y$是$x$的一个祖先,则$dp[x]=min(dp[y]+(di ...

  7. [Usaco2005 Dec]Cleaning Shifts 清理牛棚 (DP优化/线段树)

    [Usaco2005 Dec] Cleaning Shifts 清理牛棚 题目描述 Farmer John's cows, pampered since birth, have reached new ...

  8. NOI.AC#2139-选择【斜率优化dp,树状数组】

    正题 题目链接:http://noi.ac/problem/2139 题目大意 给出\(n\)个数字的序列\(a_i\).然后选出一个不降子序列最大化子序列的\(a_i\)和减去没有任何一个数被选中的 ...

  9. BZOJ3672/UOJ7 [Noi2014]购票

    本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作. 本文作者:ljh2000 作者博客:http://www.cnblogs.com/ljh2000-jump/ ...

随机推荐

  1. babel(一)

    一.babel npm babel src/index.js -d lib 二.@babel/core   @babel/cli @babel/core  转换语法核心 @babel/cli   执行 ...

  2. IdentityServer4【Topic】之定义客户端

    Defining Clients 定义客户端 客户端表示哪些可以从你的IdentityServer拿到token的应用. 除了一些可能会变化的细节之外,通常情况下你需要为一个客户端定义如下通用的设置: ...

  3. Spark源码编译,官网学习

    这里以spark-1.6.0版本为例 官网网址   http://spark.apache.org/docs/1.6.0/building-spark.html#building-with-build ...

  4. Day 6-2简单的socket通信

    什么是socket? Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口.在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面 ...

  5. C# Note30: 网络爬虫

    用C#实现网络爬虫(一) 用C#实现网络爬虫(二) 基于C#.NET的高端智能化网络爬虫(一)(反爬虫哥必看) 基于C#.NET的高端智能化网络爬虫(二)(攻破携程网) C#获取网页内容的三种方式

  6. 在 Ubuntu14.04 上搭建 Spark 2.3.1(latest version)

    搭建最新的 Spark 2.3.1 . 首先需要下载最新版 jdk .目前 2.3.1 需要 8.0 及其以上 jdk 才可以允许. 所以如果你没有 8.0  jdk 安装好了之后会报错.不要尝试安装 ...

  7. Partition算法以及其应用详解下(Golang实现)

    接前文,除了广泛使用在快速排序中.Partition算法还可以很容易的实现在无序序列中使用O(n)的时间复杂度查找kth(第k大(小)的数). 同样根据二分的思想,每完成一次Partition我们可以 ...

  8. IBM rational rose画时序图软件破解安装

    上边这个链接是开头的安装步骤,照着链接中的步骤安装完之后,接下来看下边. 1.然后安装完成打开软件“IBM Rational License Keyadministrator”.出现下图:选中第二项“ ...

  9. gevent监测单线程下的io进行切换

    from gevent import monkey;monkey.patch_all() import gevent import time def eat(name): print('%s eat ...

  10. UVA 1602 Lattice Animals

    题目 输入n.w.h($1\leqslant n \leqslant 10, 1\leqslant w,h \leqslant n$),求能放在w*h网格里的不同的n连块的个数(注意,平移.旋转.翻转 ...