【BZOJ3672】【NOI2014】购票(线段树,斜率优化,动态规划)

题解

首先考虑\(dp\)的方程,设\(f[i]\)表示\(i\)的最优值

很明显的转移\(f[i]=min(f[j]+(dep[i]-dep[j])·p[i])+q[i]\)

其中满足\(dep[i]-dep[j]\le L[i]\)

然后就可以写出一个\(O(n^2)\)的做法啦

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define RG register
#define MAX 222222
inline ll read()
{
RG ll x=0,t=1;RG char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=-1,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return x*t;
}
struct Line{int v,next,w;}e[MAX];
int h[MAX],cnt=1;
inline void Add(int u,int v,int w){e[cnt]=(Line){v,h[u],w};h[u]=cnt++;}
int n,type,fa[MAX];
ll S[MAX],P[MAX],Q[MAX],L[MAX];
ll dep[MAX],f[MAX];
void dfs(int u)
{
for(int i=h[u];i;i=e[i].next)
dep[e[i].v]=dep[u]+e[i].w,dfs(e[i].v);
}
double Slope(int a,int b){return (f[a]-f[b])*1.0/(dep[a]-dep[b]);}
namespace Brute
{
void DFS(int u)
{
if(u!=1)f[u]=1e18;
for(int i=fa[u];i&&dep[u]-dep[i]<=L[u];i=fa[i])
f[u]=min(f[u],f[i]+(dep[u]-dep[i])*P[u]+Q[u]);
for(int i=h[u];i;i=e[i].next)DFS(e[i].v);
}
void Solve()
{
DFS(1);
for(int i=2;i<=n;++i)printf("%lld\n",f[i]);
}
}
int main()
{
n=read();type=read();
for(int i=2;i<=n;++i)fa[i]=read(),S[i]=read(),P[i]=read(),Q[i]=read(),L[i]=read();
for(int i=2;i<=n;++i)Add(fa[i],i,S[i]);
dfs(1);
if(n<=2000){Brute::Solve();return 0;}
}

如果没有下面的那条限制,我们可以很容易的写出一个效率优化

设\(k<j<i\),\(j\)的转移优于\(k\),那么有

\(f[j]-dep[j]*p[i]<f[k]-dep[k]*p[i]\)

移项得到\(p[i]>\frac{f[j]-f[k]}{dep[j]-dep[k]}\)

很明显的斜率优化。

就这样我们就写出了一个\(t=0\)的\(20\)代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define RG register
#define MAX 222222
inline ll read()
{
RG ll x=0,t=1;RG char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=-1,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return x*t;
}
struct Line{int v,next,w;}e[MAX];
int h[MAX],cnt=1;
inline void Add(int u,int v,int w){e[cnt]=(Line){v,h[u],w};h[u]=cnt++;}
int n,type,fa[MAX];
ll S[MAX],P[MAX],Q[MAX],L[MAX];
ll dep[MAX],f[MAX];
void dfs(int u)
{
for(int i=h[u];i;i=e[i].next)
dep[e[i].v]=dep[u]+e[i].w,dfs(e[i].v);
}
double Slope(int a,int b){return (f[a]-f[b])*1.0/(dep[a]-dep[b]);}
namespace Task0
{
int St[MAX],top;
int check(double K)
{
int l=2,r=top,ret=1;
while(l<=r)
{
int mid=(l+r)>>1;
if(Slope(St[mid],St[mid-1])>=K)r=mid-1;
else l=mid+1,ret=mid;
}
return St[ret];
}
void Solve()
{
St[top=1]=1;
for(int i=2;i<=n;++i)
{
int j=check(P[i]);
f[i]=f[j]+Q[i]+(dep[i]-dep[j])*P[i];
while(top>1&&Slope(i,St[top-1])<Slope(St[top],St[top-1]))--top;
St[++top]=i;
printf("%lld\n",f[i]);
}
}
}
int main()
{
n=read();type=read();
for(int i=2;i<=n;++i)fa[i]=read(),S[i]=read(),P[i]=read(),Q[i]=read(),L[i]=read();
for(int i=2;i<=n;++i)Add(fa[i],i,S[i]);
dfs(1);
if(type==0){Task0::Solve();return 0;}
}

当然,再把暴力给加上就有\(40\)分了。

(自己去拼接啊,要不然代码太多了)

剩下的部分分。

对于\(t=1\),我们发现是没有距离限制的

显然是对于每一条链维护一个凸包,所以只需要维护一个可持久化栈然后在上面二分就好了。

这个东西似乎可以用主席树维护,然后在主席树上面二分就行了。

(我就懒得写了)

对于\(t=2\),是一条链,但是有距离限制,

我们发现我们会从前面开始删去凸包上的一些点,这似乎非常不好做,

但是我们似乎可以用\(CDQ\)分治来做?按照能够用来更新的区间排序

每次\(CDQ\)的时候维护一段区间的凸包,然后更新一下答案就好了

(似乎可以这样做吧。。。)


现在终于可以来讲正解之一啦

现在的问题主要是如何动态维护凸包

其实我们把树直接树链剖分之后,对应的满足条件的是\(dfs\)序上的多段区间

所以我们用线段树暴力维护凸包,每个节点开一个\(vector\)

然后暴力维护这段区间的凸包

每个点最多会被\(log\)个线段树节点所包含,复杂度\(O(nlogn)\)

然后每次询问的时候在跳重链+线段树+二分

似乎是三个\(log\),但是我觉得只有两个\(log\)

似乎二分的\(log\)是加起来才有一个\(log\)吧。。。

代码如下:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define RG register
#define MAX 222222
#define lson (now<<1)
#define rson (now<<1|1)
inline ll read()
{
RG ll x=0,t=1;RG char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=-1,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return x*t;
}
struct Line{int v,next;ll w;}e[MAX];
int h[MAX],cnt=1;
inline void Add(int u,int v,ll w){e[cnt]=(Line){v,h[u],w};h[u]=cnt++;}
int n,type,fa[MAX];
ll S[MAX],P[MAX],Q[MAX],L[MAX];
ll dis[MAX],f[MAX];
int size[MAX],top[MAX],dfn[MAX],low[MAX],ln[MAX],tim,hson[MAX];
double Slope(int a,int b){return (f[a]-f[b])*1.0/(dis[a]-dis[b]);}
void dfs(int u)
{
size[u]=1;
for(int i=h[u];i;i=e[i].next)
{
int v=e[i].v;dis[v]=dis[u]+e[i].w;
dfs(v);size[u]+=size[v];
if(size[v]>size[hson[u]])hson[u]=v;
}
}
void dfs(int u,int tp)
{
top[u]=tp;dfn[u]=++tim;ln[tim]=u;
if(hson[u])dfs(hson[u],tp);
for(int i=h[u];i;i=e[i].next)
if(e[i].v!=hson[u])dfs(e[i].v,e[i].v);
}
vector<int> t[MAX<<2];
void Modify(int now,int l,int r,int p)
{
int tp=t[now].size();
while(tp>1&&Slope(ln[p],t[now][tp-2])<Slope(t[now][tp-1],t[now][tp-2]))
--tp,t[now].pop_back();
t[now].push_back(ln[p]);
if(l==r)return;
int mid=(l+r)>>1;
if(p<=mid)Modify(lson,l,mid,p);
else Modify(rson,mid+1,r,p);
}
ll Calc(vector<int> t,int u)
{
int l=1,r=t.size()-1,ret=0;
while(l<=r)
{
int mid=(l+r)>>1;
if(Slope(t[mid],t[mid-1])<1.0*P[u])l=mid+1,ret=mid;
else r=mid-1;
}
int v=t[ret];
return f[v]+(dis[u]-dis[v])*P[u]+Q[u];
}
ll Query(int now,int l,int r,int L,int R,int u)
{
if(L<=l&&r<=R)return Calc(t[now],u);
int mid=(l+r)>>1;ll ret=1e18;
if(L<=mid)ret=min(ret,Query(lson,l,mid,L,R,u));
if(R>mid)ret=min(ret,Query(rson,mid+1,r,L,R,u));
return ret;
}
int Top=0;
void Jump(int u,int anc)
{
f[u]=5e18;int U=u;u=fa[u];
while(top[u]^top[anc])
{
f[U]=min(f[U],Query(1,1,n,dfn[top[u]],dfn[u],U));
u=fa[top[u]];
}
f[U]=min(f[U],Query(1,1,n,dfn[anc],dfn[u],U));
}
void DFS(int u)
{
S[++Top]=u;
if(u!=1)
{
int l=1,r=Top-1,v=u;
while(l<=r)
{
int mid=(l+r)>>1;
if(dis[u]-dis[S[mid]]<=L[u])v=S[mid],r=mid-1;
else l=mid+1;
}
Jump(u,v);
}
Modify(1,1,n,dfn[u]);
for(int i=h[u];i;i=e[i].next)DFS(e[i].v);
--Top;
}
int main()
{
n=read();type=read();
for(int i=2;i<=n;++i)fa[i]=read(),S[i]=read(),P[i]=read(),Q[i]=read(),L[i]=read();
for(int i=2;i<=n;++i)Add(fa[i],i,S[i]);
dfs(1);dfs(1,1);memset(S,0,sizeof(S));DFS(1);
for(int i=2;i<=n;++i)printf("%lld\n",f[i]);
return 0;
}

【BZOJ3672】【NOI2014】购票(线段树,斜率优化,动态规划)的更多相关文章

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

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

  2. UOJ#7. 【NOI2014】购票 | 线段树 凸包优化DP

    题目链接 UOJ #7 题解 首先这一定是DP!可以写出: \[f[i] = \min_{ancestor\ j} \{f[j] + (d[j] - d[i]) * p[i] + q[i]\}\] 其 ...

  3. [NOI2014]购票 「树上斜率优化」

    首先易得方程,且经过变换有 $$\begin{aligned} f_i &= \min\limits_{dist_i - lim_i \le dist_j} \{f_j + (dist_i - ...

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

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

  5. BZOJ3672: [Noi2014]购票【CDQ分治】【点分治】【斜率优化DP】

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

  6. BZOJ3672 [Noi2014]购票 【点分治 + 斜率优化】

    题目链接 BZOJ3672 题解 如果暂时不管\(l[i]\)的限制,并假使这是一条链 设\(f[i]\)表示\(i\)节点的最优答案,我们容易得到\(dp\)方程 \[f[i] = min\{f[j ...

  7. bzoj3672: [Noi2014]购票(树形DP+斜率优化+可持久化凸包)

    这题的加强版,多了一个$l_i$的限制,少了一个$p_i$的单调性,难了好多... 首先有方程$f(i)=min\{f(j)+(dep_i-dep_j)*p_i+q_i\}$ $\frac {f(j) ...

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

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

  9. UOJ#7. 【NOI2014】购票 点分治 斜率优化 凸包 二分

    原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ7.html 题解 这题是Unknown的弱化版. 如果这个问题出在序列上,那么显然可以CDQ分治 + 斜率 ...

  10. bzoj千题计划251:bzoj3672: [Noi2014]购票

    http://www.lydsy.com/JudgeOnline/problem.php?id=3672 法一:线段树维护可持久化单调队列维护凸包 斜率优化DP 设dp[i] 表示i号点到根节点的最少 ...

随机推荐

  1. 记Thinkpad的一次扩容升级经历

    俗话说:" 工欲善其事,必先利其器" 阅读目录: 背景 目标 准备 友情提示 制作引导盘 分区及备份 拆机装盘 重装系统 写在结束的 参考资料 背景: 作为一个近六年的IT从业Co ...

  2. Cannot get connection for URL jdbc:oracle:thin:调用中无效参数

    这个报错明显是连接数据库的url没有写对,但是,我要说的是但是,同样的代码生产没有问题,而测试环境报错了.最终哥找到那个错误,jdbc连接数据库时,有ResultSet,PreparedStateme ...

  3. sqlite两表更新update

    1 2 3 4 5 6 7 8 9 10 11 12 UPDATE t1 SET Column1 =   ( SELECT Columnx    FROM t2    WHERE t2. KEY = ...

  4. 如何下载YouTube 60fps视频

    YouTube上面不仅支持分辨率为4K和8K的视频,同时也开启了对60fps视频的支持.60帧的视频广泛用于游戏和体育视频中,使视频看起来更加流畅和细腻.对游戏玩家来说,YouTube对60fps支持 ...

  5. [C++]C++得到最大的int值

    要得到最大的int值: 利用(unsigned int)-1,这样得到的就是unsigned int表示的最大值, int值只是比unsigned int多一位符号位,所以对(unsigned int ...

  6. MATLAB 笔记

    MATLAB的学习 Matlab 主要有5大部分构成,分别是MATLAB语言,桌面工具与开发环境,数学函数库 ,图形系统和应用程序接口.以及众多的专业工具.

  7. 17 Tips For Writing An Excellent Email Subject Line

    Out of the billions of emails that are sent every day, how can you make sure that yours stands out? ...

  8. 下载android sdk更新包离线安装解决方案

    本文转载自:http://xljboox.blog.163.com/blog/static/7628448320111159354738/ 第一次安装android sdk后进行开发包的更新,你应该了 ...

  9. python正则表达式re之compile函数解析

    re正则表达式模块还包括一些有用的操作正则表达式的函数.下面主要介绍compile函数. 定义: compile(pattern[,flags] ) 根据包含正则表达式的字符串创建模式对象. 通过py ...

  10. win10 redis安装教程

    下载解压,没什么好说的,在解压后的目录下有以下这些文件: 在 命令行 启动服务端 redis目录下执行: redis-server.exe redis.windows.conf 如果需要 开机启动:执 ...