高速公路
作者:互联网
LXXVI.高速公路
简直恶心到爆炸……
首先,暴力的DP是非常简单的。设\(dis_x\)表示位置\(x\)到根的距离,则有
\[f_x=\min\limits_{y\text{ is an ancestor of }x}f_y+p_x(dis_x-dis_y)+q_x \]暴力一敲,期望得分\(40\%\)。由于数据可能水了,实际得分\(60\%\)。
代码:
#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;
}
考虑斜率优化一下。
我们先把问题抽象到序列上。设有一个\(j<k<i\),则如果\(j\)比\(k\)优,必有
\[f_j+p_i(dis_i-dis_j)+q_i\leq f_k+p_i(dis_i-dis_k)+q_i \]\[f_j+p_idis_i-p_idis_j\leq f_k+p_idis_i-p_idis_k \]\[f_j-f_k\leq p_idis_j-p_idis_k \]\[f_j-f_k\leq p_i(dis_j-dis_k) \]\[\dfrac{f_j-f_k}{dis_j-dis_k}\geq p_i \]然后直接优化即可,因为\(p_i\)保证递增。
但是,因为是在树上,所以你从一颗子树中跑出来之后,还要复原原序列!
当然,复原是很简单的,因为斜率优化的过程中除了队首队尾的移动以外,唯一真正改动的位置就是最后的入队环节。这时候只需要记录这个位置原本放了什么即可。
但是,暴力地维护队列的话,就不能完美地应用“单调队列”的性质了,因此复杂度最劣仍然是\(O(n^2)\)。期望得分\(60\%\)(听说常卡的好也能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;
}
发现我们每个位置都那么费劲地移动左右端点太蠢了。不如直接套上一个二分。复杂度是妥妥的\(O(n\log n)\)。
(这二分真的非常难写,各种边界太恶心了)
代码:
#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;
}
标签:head,int,1001000,mid,高速公路,edge,dis 来源: https://www.cnblogs.com/Troverld/p/14598423.html