【LuoguP3994】高速公路

题目链接

题目描述

C国拥有一张四通八达的高速公路网树,其中有n个城市,城市之间由一共n-1条高速公路连接。除了首都1号城市,每个城市都有一家本地的客运公司,可以发车前往全国各地,有若干条高速公路连向其他城市,这是一个树型结构,1号城市(首都)为根。假设有一个人要从i号城市坐车出发前往j号城市,那么他要花费Pi*(i城市到j城市的距离)+Qi元。由于距离首都越远,国家的监管就越松,所以距离首都越远,客运公司的Pi(单位距离价格)越大,形式化的说,如果把高速路网看成一棵以首都为根的有根树,i号城市是j号城市的某个祖先,那么一定存在Pi<=Pj。

Sol

假装我们只用考虑从祖先转移过来

那么化下式子就是:

f [   i   ] = m i n { f [   j   ] P [   i   ] D i s [   j   ] } + Q [   i   ] + P [   i   ] D i s [   i   ]

里面就是个裸的斜率优化了
然而显然直接做是不行的,因为这是一颗树

那么就需要对斜率优化有更深刻的理解才能做了

一种暴力的做法是用可持久化线段树来维护单调队列的数组

但是主意到我们每次考虑一个元素时只是更改了首尾的指针,并且还修改了至多队列中的一个元素(被当前元素覆盖了),那么我们可以直接暴力撤销操作,获得满分

但其实这样做复杂度是不对的,因为这样你会发现一个元素不仅仅是入队出队了一次,而是可能被反复加进来,这样就会被卡掉

因为斜率优化的实质是维护了一个凸包,也就是说里面直线的斜率是单调的,那么我们可以不用暴力出对,而改成二分这个位置然后一次性出队,这样复杂度就变成了稳定的 O ( n l o g n )

(然而能直接过有什么不好的)

暴力出队:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<cmath>
#include<set>
using namespace std;
inline int read()
{
    int x=0;char ch=getchar();int t=1;
    for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-' )t=-1;
    for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch-48);
    return x*t;
}
const int N=1e6+10;
struct edge{
    int to,next;
}a[N];
int cur[N];int cnt=0;
inline void add(int x,int y){a[++cnt]=(edge){y,cur[x]};cur[x]=cnt;}
typedef long long ll;
int P[N];
int Q[N];
int fa[N],W[N];
int n;
int que[N];int h,t;
ll dp[N],dis[N];
int head[N],tail[N],pos[N],pre[N];
typedef double db;
inline db getk(int p,int q){
    return (db)(dp[p]-dp[q])/(db)(dis[p]-dis[q]);
}
void dfs(int u)
{
    head[u]=h;tail[u]=t;
    while(h<t&&getk(que[h],que[h+1])<=1.00*P[u]) ++h;
    dp[u]=dp[que[h]]+(dis[u]-dis[que[h]])*P[u]+Q[u];
    while(h<t&&getk(que[t],que[t-1])>getk(u,que[t])) --t;
    pre[u]=que[++t];que[t]=u;pos[u]=t;
    for(register int v,i=cur[u];i;i=a[i].next){
        v=a[i].to;dis[v]=dis[u]+W[v];dfs(v);
    }
    h=head[u];t=tail[u];que[pos[u]]=pre[u];
    return;
}
int main()
{
    n=read();
    for(register int i=2;i<=n;++i) fa[i]=read(),W[i]=read(),P[i]=read(),Q[i]=read(),add(fa[i],i);
    h=1;t=0;
    dfs(1);
    for(register int i=2;i<=n;++i) printf("%lld\n",dp[i]);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/element_hero/article/details/82714163