高速公路
LXXVI.高速公路
简直恶心到爆炸……
首先,暴力的DP是非常简单的。设表示位置到根的距离,则有
暴力一敲,期望得分。由于数据可能水了,实际得分。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,head[1001000],cnt,a[1001000],b[1001000],dis[1001000];
ll f[1001000];
struct node{
int to,next,val;
}edge[1001000];
void ae(int u,int v,int w){
edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
}
vector<int>v;
void dfs(int x){
for(auto y:v)f[x]=min(f[x],1ll*a[x]*(dis[x]-dis[y])+b[x]+f[y]);
v.push_back(x);
for(int i=head[x];i!=-1;i=edge[i].next)dis[edge[i].to]=dis[x]+edge[i].val,dfs(edge[i].to);
v.pop_back();
}
int main(){
scanf("%d",&n),memset(head,-1,sizeof(head)),memset(f,0x3f3f3f3f,sizeof(f)),f[1]=0;
for(int i=2,x,y;i<=n;i++)scanf("%d%d%d%d",&x,&y,&a[i],&b[i]),ae(x,i,y);
dfs(1);
for(int i=2;i<=n;i++)printf("%lld\n",f[i]);
return 0;
}
考虑斜率优化一下。
我们先把问题抽象到序列上。设有一个,则如果比优,必有
然后直接优化即可,因为保证递增。
但是,因为是在树上,所以你从一颗子树中跑出来之后,还要复原原序列!
当然,复原是很简单的,因为斜率优化的过程中除了队首队尾的移动以外,唯一真正改动的位置就是最后的入队环节。这时候只需要记录这个位置原本放了什么即可。
但是,暴力地维护队列的话,就不能完美地应用“单调队列”的性质了,因此复杂度最劣仍然是。期望得分(听说常卡的好也能A?)
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,head[1001000],cnt,a[1001000],b[1001000],q[1001000],l[1001000],r[1001000],cha[100100];
ll f[1001000],dis[1001000];
void read(int &x){
x=0;
char c=getchar();
while(c>'9'||c<'0')c=getchar();
while(c>='0'&&c<='9')x=x*10+c-48,c=getchar();
}
struct node{
int to,next,val;
}edge[1001000];
void ae(int u,int v,int w){
edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
}
void dfs(int x){
while(l[x]<r[x]&&(f[q[l[x]]]-f[q[l[x]+1]])>=(dis[q[l[x]]]-dis[q[l[x]+1]])*a[x])l[x]++;
f[x]=(dis[x]-dis[q[l[x]]])*a[x]+b[x]+f[q[l[x]]];
while(l[x]<r[x]&&(f[q[r[x]-1]]-f[q[r[x]]])*(dis[q[r[x]]]-dis[x])>=(f[q[r[x]]]-f[x])*(dis[q[r[x]-1]]-dis[q[r[x]]]))r[x]--;
cha[x]=q[++r[x]],q[r[x]]=x;
printf("%d:%d,%d:",x,l[x],r[x]);for(int i=l[x];i<=r[x];i++)printf("%d ",q[i]);puts("");
for(int i=head[x];i!=-1;i=edge[i].next)dis[edge[i].to]=dis[x]+edge[i].val,l[edge[i].to]=l[x],r[edge[i].to]=r[x],dfs(edge[i].to);
q[r[x]]=cha[x];
}
int main(){
read(n),memset(head,-1,sizeof(head));
for(int i=2,x,y;i<=n;i++)read(x),read(y),read(a[i]),read(b[i]),ae(x,i,y);
l[1]=1,dfs(1);
for(int i=2;i<=n;i++)printf("%lld\n",f[i]);
return 0;
}
发现我们每个位置都那么费劲地移动左右端点太蠢了。不如直接套上一个二分。复杂度是妥妥的。
(这二分真的非常难写,各种边界太恶心了)
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,head[1001000],cnt,a[1001000],b[1001000],q[1001000],l[1001000],r[1001000],cha[1001000];
ll f[1001000],dis[1001000];
void read(int &x){
x=0;
char c=getchar();
while(c>'9'||c<'0')c=getchar();
while(c>='0'&&c<='9')x=x*10+c-48,c=getchar();
}
struct node{
int to,next,val;
}edge[1001000];
void ae(int u,int v,int w){
edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
}
void dfs(int x){
int L,R,res;
L=l[x],R=r[x]-1,res=r[x];
while(L<=R){
int mid=(L+R)>>1;
if((f[q[mid]]-f[q[mid+1]])>=(dis[q[mid]]-dis[q[mid+1]])*a[x])L=mid+1;
else R=mid-1,res=mid;
}
l[x]=res;
f[x]=(dis[x]-dis[q[l[x]]])*a[x]+b[x]+f[q[l[x]]];
L=l[x]+1,R=r[x],res=l[x];
while(L<=R){
int mid=(L+R)>>1;
if((f[q[mid-1]]-f[q[mid]])*(dis[q[mid]]-dis[x])>=(f[q[mid]]-f[x])*(dis[q[mid-1]]-dis[q[mid]]))R=mid-1;
else res=mid,L=mid+1;
}
r[x]=res;
cha[x]=q[++r[x]],q[r[x]]=x;
for(int i=head[x];i!=-1;i=edge[i].next)dis[edge[i].to]=dis[x]+edge[i].val,l[edge[i].to]=l[x],r[edge[i].to]=r[x],dfs(edge[i].to);
q[r[x]]=cha[x];
}
int main(){
read(n),memset(head,-1,sizeof(head));
for(int i=2,x,y;i<=n;i++)read(x),read(y),read(a[i]),read(b[i]),ae(x,i,y);
l[1]=1,dfs(1);
for(int i=2;i<=n;i++)printf("%lld\n",f[i]);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?