【洛谷p3994】Highway 二分+斜率优化DP
题目大意:给你一颗n个点的有根树,相邻两个点之间有距离,我们可以从x乘车到x的祖先,费用为dis×P[x]+Q[x],问你除根以外每个点到根的最小花费。
数据范围:n≤106。
此题我们显然dp,列出方程为f[x]=min{f[y]+dis(x,y)×P[x]+Q[x]},其中y为x的祖先。
不难看出可能是一个斜率优化的式子,我们往下推推
设i是j的祖先,且从i出转移比从j处转移劣,不难列出:
f[i]+dis(i,x)P[x]+Q[x]>f[j]+dis(j,x)P[x]+Q[x]
化简化简,令Di表示从根到i的距离,继续移项化简
f[i]−f[j]>P[x]((Dx−Dj)−(Dx−Di))
f[i]−f[j]>P[x](Di−Dj)
f[i]−f[j]Di−Dj<P[x](注意大于符号变成小于,因为Di<Dj,重要!,我被坑了)
考虑到P随着点深度递增,于是就可以快乐斜率优化了。
不过这题需要在树上转移,当遍历完一个子树后,我们要把单调队列恢复,恢复操作显然可以用可持久化线段树来搞。
其实不用这么复杂,考虑到只存在移动队头,移动队尾&在队尾加一个数的操作,我们只需要记录下以前的队头/尾的情况,还有被覆盖的数原先是啥,就可以在常数时间内恢复回去。
然而这么做的话无法保证移动队头尾的次数是线性的(我们搞一个链+链底菊花树),所以在找队头和队尾时,需要用二分。
于是时间复杂度就变成O(nlog n)了,二分的常数很小,问题不大。
(我比较懒写了暴力移动的,二分的话自己脑补吧qwq)
1 #include<bits/stdc++.h> 2 #define M 1000005 3 #define L long long 4 using namespace std; 5 6 struct edge{L u,v,next;}e[M]={0}; L head[M]={0},use=0; 7 void add(L x,L y,L z){use++;e[use].u=y;e[use].v=z;e[use].next=head[x];head[x]=use;} 8 L n,f[M]={0},P[M]={0},Q[M]={0},dis[M]={0},q[M]={0},h=1,t=0; 9 10 double slope(L i,L j){return 1.*(f[i]-f[j])/(dis[i]-dis[j]);} 11 12 void dfs(L x,L D){ 13 L H=h,T=t; 14 while(h<t&&P[x]>slope(q[h],q[h+1])) h++; 15 f[x]=f[q[h]]+((dis[x]=D)-dis[q[h]])*P[x]+Q[x]; 16 while(h<t&&slope(q[t-1],q[t])>slope(q[t],x)) t--; 17 L lastT=q[++t]; q[t]=x; 18 for(L i=head[x];i;i=e[i].next) dfs(e[i].u,D+e[i].v); 19 q[t]=lastT; h=H; t=T; 20 } 21 22 main(){ 23 scanf("%lld",&n); 24 for(L i=2;i<=n;i++){ 25 L fa,dis; scanf("%lld%lld%lld%lld",&fa,&dis,P+i,Q+i); 26 add(fa,i,dis); 27 } 28 q[t=1]=1; for(L i=head[1];i;i=e[i].next) dfs(e[i].u,e[i].v); 29 for(L i=2;i<=n;i++) printf("%lld\n",f[i]); 30 }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!