bzoj千题计划251:bzoj3672: [Noi2014]购票
http://www.lydsy.com/JudgeOnline/problem.php?id=3672
法一:线段树维护可持久化单调队列维护凸包 斜率优化DP
设dp[i] 表示i号点到根节点的最少花费
dis[i] 表示 点i到根节点的距离
dp[i]= min { (dis[i]-dis[j])* P[i] + Q[i] + dp[j] } j是i的祖先且dis[i]-dis[j]<=L[i]
即 dp[i]+dis[j]*P[i]=dp[j]+dis[i]*P[i]+Q[i]
斜率优化,dp[i]=斜率为P[i] 的直线 截(dis[j],dp[j])所得的截距最小值
如果不考虑L[i] 的限制:
那就可以用一个可持久化的单调队列维护 根节点到当前点的路径上所有的点构成的下凸壳
考虑如何去除兄弟节点的子树对单调队列的影响
即在一个节点退出dfs时,将单调队列恢复为这个节点开始dfs的情况
头指针:头指针的前移不会涉及单调队列元素的修改,因为从根往下走不能保证斜率单调,所以头指针不能出队,需要二分找到最小的 斜率>P[i] 的点来更新dp[i]
尾指针:尾指针的前移会涉及到单调队列元素的替换,但替换只会替换一个元素,所以记录下 替换的是谁,原来的尾指针是谁,退出的时候恢复即可
加上L[i]的限制:
用线段树维护,若线段树节点的区间为[l,r],那这个节点维护的是当前链上 深度为[l,r]的点构成的凸包
即由原来的维护一个单调队列变成维护nlogn个单调队列
每个节点开一个vector? 光开nlogn个vector就TLE了吧
开一个nlogn的数组v存储整条链上凸包内的点
在建树的时候,按节点的顺序给头指针分一个位置x,若节点的大小为y,
那么这个节点维护的单调队列 中的点会出现在v数组的[x,x+y-1]位置
这样线段树内只需要存储单调队列的头尾指针即可
单调队列采用 左闭右开 的好处:
多个可持久化单调队列,头尾指针相连
如果头尾指针是左闭右闭
如果队列为空,tail<head,导致 后一个队列的尾指针=前一个队列的头指针
这样尾指针替换更改,记录的替换位置为tail,即前一个队列的head
退出dfs替换回去 错误的替换了前一个队列的head
头尾指针左闭右开就不会有这个问题
- #include<cstdio>
- #include<iostream>
- #include<algorithm>
- using namespace std;
- #define N 200001
- typedef long long LL;
- int n;
- int P[N];
- LL Q[N],L[N];
- int front[N],to[N],nxt[N],tot;
- LL val[N];
- struct node
- {
- int head,tail;
- }tr[N<<];
- int v[N*];
- int sum;
- LL dep[N],dis[N];
- LL dp[N];
- int re_t[N][],re_w[N][];
- template<typename T>
- void read(T &x)
- {
- x=; char c=getchar();
- while(!isdigit(c)) c=getchar();
- while(isdigit(c)) { x=x*+c-''; c=getchar(); }
- }
- void add(int u,int v,LL w)
- {
- to[++tot]=v; nxt[tot]=front[u]; front[u]=tot; val[tot]=w;
- }
- void build(int k,int l,int r)
- {
- tr[k].head=sum+;
- tr[k].tail=tr[k].head;
- sum+=r-l+;
- if(l==r) return;
- int mid=l+r>>;
- build(k<<,l,mid);
- build(k<<|,mid+,r);
- }
- inline double slope(int i,int j)
- {
- return 1.0*(dp[j]-dp[i])/(dis[j]-dis[i]);
- }
- int find_ans(int k,int p)
- {
- int l=tr[k].head;
- int r=tr[k].tail-;
- if(l==r) return v[l];
- r--;
- int mid,tmp=-;
- while(l<=r)
- {
- mid=l+r>>;
- if(p<slope(v[mid],v[mid+])) tmp=mid,r=mid-;
- else l=mid+;
- }
- if(tmp==-) return v[tr[k].tail-];
- return v[tmp];
- }
- int query(int k,int l,int r,int opl,int opr,int p)
- {
- if(l>=opl && r<=opr) return find_ans(k,p);
- int mid=l+r>>;
- if(opr<=mid) return query(k<<,l,mid,opl,opr,p);
- else if(opl>mid) return query(k<<|,mid+,r,opl,opr,p);
- else
- {
- int t1=query(k<<,l,mid,opl,opr,p);
- int t2=query(k<<|,mid+,r,opl,opr,p);
- if(p<slope(t1,t2)) return t1;
- return t2;
- }
- }
- int find(int r,LL lim)
- {
- int l=;
- int tmp,mid;
- while(l<=r)
- {
- mid=l+r>>;
- if(dep[mid]>=lim) tmp=mid,r=mid-;
- else l=mid+;
- }
- return tmp;
- }
- int in_queue(int k,int j)
- {
- if(tr[k].head==tr[k].tail) return tr[k].head;
- int l=tr[k].head,r=tr[k].tail-,tmp=-,mid;
- while(l<=r)
- {
- mid=l+r>>;
- if(slope(v[mid],v[mid+])>slope(v[mid],j)) tmp=mid+,r=mid-;
- else l=mid+;
- }
- if(tmp==-) return tr[k].tail;
- return tmp;
- }
- void insert(int k,int l,int r,int x,int w,int d)
- {
- int pos=in_queue(k,w);
- re_t[w][d]=tr[k].tail;
- re_w[w][d]=v[pos];
- v[pos]=w;
- tr[k].tail=pos+;
- if(l==r) return;
- int mid=l+r>>;
- if(x<=mid) insert(k<<,l,mid,x,w,d+);
- else insert(k<<|,mid+,r,x,w,d+);
- }
- void del(int k,int l,int r,int x,int w,int d)
- {
- v[tr[k].tail-]=re_w[w][d];
- tr[k].tail=re_t[w][d];
- if(l==r) return;
- int mid=l+r>>;
- if(x<=mid) del(k<<,l,mid,x,w,d+);
- else del(k<<|,mid+,r,x,w,d+);
- }
- void dfs(int x,int d)
- {
- if(x!=)
- {
- LL lim=dis[x]-L[x];
- int j=query(,,n,find(d-,lim),d-,P[x]);
- dp[x]=(dis[x]-dis[j])*P[x]+Q[x]+dp[j];
- }
- insert(,,n,d,x,);
- for(int i=front[x];i;i=nxt[i])
- {
- dis[to[i]]=dis[x]+val[i];
- dep[d+]=dep[d]+val[i];
- dfs(to[i],d+);
- }
- del(,,n,d,x,);
- }
- int main()
- {
- int t;
- read(n); read(t);
- int f; LL s;
- for(int i=;i<=n;++i)
- {
- read(f); read(s); read(P[i]); read(Q[i]); read(L[i]);
- add(f,i,s);
- }
- sum=; build(,,n);
- insert(,,n,,,);
- dfs(,);
- for(int i=;i<=n;++i) cout<<dp[i]<<'\n';
- return ;
- }
法二、点分治+斜率优化dp
朴素的dp是往上找能更新它的点
这里往下找它能更新的点
具体做法:
找出分治重心后,先 解决重心 的祖先(包括重心)那块儿
然后求出 原树根节点到重心的凸包 更新重心的子树
至于 距离的限制,我们把重心的子树中所有点 按能更新它的 深度最小的城市 从大到小排序
枚举这些点i
构造重心到原树根节点的凸包时,从重心往上依次加点,只加入 深度>= 当前点i深度限制 的点
- #include<cstdio>
- #include<iostream>
- #include<algorithm>
- using namespace std;
- #define N 200001
- typedef long long LL;
- int n;
- int front[N],nxt[N],to[N],tot;
- LL val[N];
- int fa[N],P[N];
- LL Q[N],L[N];
- LL dis[N];
- bool vis[N];
- int root;
- int siz[N],mx[N];
- struct node
- {
- int id;
- LL lim;
- }e[N];
- int cnt;
- int st[N];
- LL dp[N];
- template<typename T>
- void read(T &x)
- {
- x=; char c=getchar();
- while(!isdigit(c)) c=getchar();
- while(isdigit(c)) { x=x*+c-''; c=getchar(); }
- }
- void add(int u,int v,LL w)
- {
- to[++tot]=v; nxt[tot]=front[u]; front[u]=tot; val[tot]=w;
- }
- void dfs(int x)
- {
- for(int i=front[x];i;i=nxt[i])
- {
- dis[to[i]]=dis[x]+val[i];
- dfs(to[i]);
- }
- }
- void get_siz(int x,int all)
- {
- siz[x]=; mx[x]=;
- for(int i=front[x];i;i=nxt[i])
- if(!vis[to[i]])
- {
- get_siz(to[i],all);
- siz[x]+=siz[to[i]];
- if(siz[to[i]]>mx[x]) mx[x]=siz[to[i]];
- }
- mx[x]=max(mx[x],all-siz[x]);
- if(mx[x]<mx[root] && siz[x]>) root=x;
- }
- void dfs2(int x)
- {
- e[++cnt].id=x;
- e[cnt].lim=dis[x]-L[x];
- for(int i=front[x];i;i=nxt[i])
- if(!vis[to[i]]) dfs2(to[i]);
- }
- bool cmp(node p,node q)
- {
- return p.lim>q.lim;
- }
- double slope(int i,int j)
- {
- return 1.0*(dp[j]-dp[i])/(dis[j]-dis[i]);
- }
- void solve(int x,int all)
- {
- if(all==) return;
- root=;
- get_siz(x,all);
- int rt=root;
- for(int i=front[root];i;i=nxt[i])
- vis[to[i]]=true;
- solve(x,all-siz[root]+);
- cnt=;
- for(int i=front[rt];i;i=nxt[i])
- dfs2(to[i]);
- sort(e+,e+cnt+,cmp);
- int now=rt,top=;
- for(int i=;i<=cnt;++i)
- {
- while(now!=fa[x] && e[i].lim<=dis[now])
- {
- while(top> && slope(st[top],st[top-])<slope(now,st[top])) top--;
- st[++top]=now;
- now=fa[now];
- }
- if(top)
- {
- int l=,r=top-,mid,tmp=-;
- while(l<=r)
- {
- mid=l+r>>;
- if(slope(st[mid],st[mid+])>=P[e[i].id]) tmp=mid+,l=mid+;
- else r=mid-;
- }
- if(tmp==-) tmp=;
- dp[e[i].id]=min(dp[e[i].id],(dis[e[i].id]-dis[st[tmp]])*P[e[i].id]+Q[e[i].id]+dp[st[tmp]]);
- }
- }
- for(int i=front[rt];i;i=nxt[i])
- solve(to[i],siz[to[i]]);
- }
- int main()
- {
- int t;
- read(n); read(t);
- LL s;
- for(int i=;i<=n;++i)
- {
- read(fa[i]); read(s); read(P[i]); read(Q[i]); read(L[i]);
- add(fa[i],i,s);
- }
- dfs();
- mx[]=n+;
- for(int i=;i<=n;++i) dp[i]=2e17+1e12;
- solve(,n);
- for(int i=;i<=n;++i) cout<<dp[i]<<'\n';
- }
3672: [Noi2014]购票
Time Limit: 30 Sec Memory Limit: 512 MB
Submit: 1520 Solved: 768
[Submit][Status][Discuss]
Description
Input
第 1 行包含2个非负整数 n,t,分别表示城市的个数和数据类型(其意义将在后面提到)。输入文件的第 2 到 n 行,每行描述一个除SZ之外的城市。其中第 v 行包含 5 个非负整数 f_v,s_v,p_v,q_v,l_v,分别表示城市 v 的父亲城市,它到父亲城市道路的长度,票价的两个参数和距离限制。请注意:输入不包含编号为 1 的SZ市,第 2 行到第 n 行分别描述的是城市 2 到城市 n。
Output
输出包含 n-1 行,每行包含一个整数。其中第 v 行表示从城市 v+1 出发,到达SZ市最少的购票费用。同样请注意:输出不包含编号为 1 的SZ市。
Sample Input
1 2 20 0 3
1 5 10 100 5
2 4 10 10 10
2 9 1 100 10
3 5 20 100 10
4 4 20 0 10
Sample Output
150
70
149
300
150
bzoj千题计划251:bzoj3672: [Noi2014]购票的更多相关文章
- bzoj千题计划300:bzoj4823: [Cqoi2017]老C的方块
http://www.lydsy.com/JudgeOnline/problem.php?id=4823 讨厌的形状就是四联通图 且左右各连一个方块 那么破坏所有满足条件的四联通就好了 按上图方式染色 ...
- bzoj千题计划250:bzoj3670: [Noi2014]动物园
http://www.lydsy.com/JudgeOnline/problem.php?id=3670 法一:KMP+st表 抽离nxt数组,构成一棵树 若nxt[i]=j,则i作为j的子节点 那么 ...
- bzoj千题计划238:bzoj3668: [Noi2014]起床困难综合症
http://www.lydsy.com/JudgeOnline/problem.php?id=3668 这..一位一位的来就好了呀 #include<cstdio> #include&l ...
- bzoj千题计划196:bzoj4826: [Hnoi2017]影魔
http://www.lydsy.com/JudgeOnline/problem.php?id=4826 吐槽一下bzoj这道题的排版是真丑... 我还是粘洛谷的题面吧... 提供p1的攻击力:i,j ...
- bzoj千题计划280:bzoj4592: [Shoi2015]脑洞治疗仪
http://www.lydsy.com/JudgeOnline/problem.php?id=4592 注意操作1 先挖再补,就是补的范围可以包含挖的范围 SHOI2015 的题 略水啊(逃) #i ...
- bzoj千题计划177:bzoj1858: [Scoi2010]序列操作
http://www.lydsy.com/JudgeOnline/problem.php?id=1858 2018 自己写的第1题,一遍过 ^_^ 元旦快乐 #include<cstdio> ...
- bzoj千题计划317:bzoj4650: [Noi2016]优秀的拆分(后缀数组+差分)
https://www.lydsy.com/JudgeOnline/problem.php?id=4650 如果能够预处理出 suf[i] 以i结尾的形式为AA的子串个数 pre[i] 以i开头的形式 ...
- bzoj千题计划304:bzoj3676: [Apio2014]回文串(回文自动机)
https://www.lydsy.com/JudgeOnline/problem.php?id=3676 回文自动机模板题 4年前的APIO如今竟沦为模板,,,╮(╯▽╰)╭,唉 #include& ...
- bzoj千题计划292:bzoj2244: [SDOI2011]拦截导弹
http://www.lydsy.com/JudgeOnline/problem.php?id=2244 每枚导弹成功拦截的概率 = 包含它的最长上升子序列个数/最长上升子序列总个数 pre_len ...
随机推荐
- 新的一年新的变化!IT的大变天
今天是一个特别的日子,祝女神朋友们,节日快乐,早点下班! 新的一年,大家又忙碌在加班加点的堆代码中,bug的陪伴使我快乐使我忧伤,想想想,也奋斗了五六百的岁月,实习期向往大城市的公司,梦想着有一天与自 ...
- hadoop第一课
Hadoop基本概念 在当下的IT领域,大数据很"热",实现大数据场 景的Hadoop系列产品更"热". Hadoop是一个开源的分布式系统基础架构,由 Apa ...
- iframe结构的项目,登录页面嵌套
参考:http://www.cnblogs.com/qixin622/p/6548076.html 在网页编程时,我们经常需要处理,当session过期时,我们要跳到登陆页面让用户登陆,由于我们可能用 ...
- CSS3 translate、transform、transition区别
translate:移动, transform的一个方法 通过 translate() 方法,元素从其当前位置移动,根据给定的 left(x 坐标) 和 top(y 坐标) ...
- php 后端跨域请求
header("Access-Control-Allow-Origin: http://a.com"); // 允许a.com发起的跨域请求 //如果需要设置允许所有域名发起的跨域 ...
- PAT乙级-1041. 考试座位号(15)
每个PAT考生在参加考试时都会被分配两个座位号,一个是试机座位,一个是考试座位.正常情况下,考生在入场时先得到试机座位号码,入座进入试机状态后,系统会显示该考生的考试座位号码,考试时考生需要换到考试座 ...
- 巧用UserAgent来解决浏览器的各种问题
以前对UserAgent了解不是很透彻,今天发现UserAgent用处多多.比如我之前一直很喜欢用火狐浏览器,不过用了那么久发现火狐浏览器问题多多,比如有的论坛上传附件或者上传图片等按钮没有作用,并且 ...
- selenium webdriver 的环境搭建时注意事项
selenium webdriver 在 eclipse中的配置,网络上应该很方便搜索到,这里只记搭建过程中容易出现的一些问题 1. selenium-java与selenium-sever-sta ...
- neo智能合约的生命周期
- linux性能调试之vmstat
linux性能监控调优工具vmstat: vmstat:用于监控.显示系统运行过程中的虚拟内存/CPU/磁盘状态. 简单示例(时间间隔2s,监控2次): 重要字段解释: r 表示运行队列(等待运行的进 ...