【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. OpenLDAP备份和恢复

    OpenLDAP中数据备份一般分为二种: 1)通过slapcat 指令进行备份 2)通过phpLDAPadmin控制台进行备份 备份方式1: 1)slapcat -v -l openldap-back ...

  2. Numpy入门笔记第二天

    # 数组的组合 import numpy as np arr1 = np.arange(5) arr2 = np.arange(3) print arr1 print arr2 [0 1 2 3 4] ...

  3. Kafka安装之二 在CentOS 7上安装Kafka

    一.简介 Kafka是由Apache软件基金会开发的一个开源流处理平台,由Scala和Java编写.Kafka是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者规模的网站中的所有动作流数据. 这 ...

  4. 微软职位内部推荐-SW Engineer II for Windows System

    微软近期Open的职位: Microsoft's Operating Systems Group delivers the operating system and core user experie ...

  5. Scrum立会报告+燃尽图(Beta阶段第二周第七次)

    此作业要求参见:https://edu.cnblogs.com/campus/nenu/2018fall/homework/2415 项目地址:https://coding.net/u/wuyy694 ...

  6. "Hello World!"团队第十次会议

    Scrum会议 今天是我们"Hello World!"团队第十次召开会议,博客内容是: 1.会议时间 2.会议成员 3.会议地点 4.会议内容 5.todo list 6.会议照片 ...

  7. 0527 SCRUM团队项目7.0

    Sprint回顾 让我们一次比一次做得更好.   1.回顾组织 主题:“我们怎样才能在下个sprint中做的更好?” 时间:设定为1至2个小时. 参与者:整个团队. 场所:能够在不受干扰的情况下讨论. ...

  8. C++ Primer Plus学习:第七章

    C++入门第七章:函数-C++的编程模块 函数的基本知识 要使用C++函数,必须完成如下工作: 提供函数定义 提供函数原型 调用函数 库函数是已经定义和编译好的函数,可使用标准库头文件提供原型. 定义 ...

  9. Myeclipse错误:Errors occurred during the build. Errors running builder 'DeploymentBuilder' on project ...解决方法

    解决办法:1.首先关闭MyEclipse工作空间.2.然后删除工作空间下的“/.metadata/.plugins/org.eclipse.core.runtime/.settings/com.gen ...

  10. java沙盒入门

    程序员写一个Java程序,默认的情况下你可以访问任意的机器资源,比如读取,删除一些文件或者网络操作等.当你把程序部署到正式的服务器上,系统管理员要为服务器的安全承担责任,那么他可能不敢确定你的程序会不 ...