https://www.luogu.org/problemnew/show/P3994

 

设dp[i] 表示第i个城市到根节点的最小花费

dp[i]=min{ (dis[i]-dis[j])*P[i]+Q[i]+dp[j] } 

这是O(n^2)的

这个式子可以斜率优化

dp[i]+dis[j]*P[i]=dis[i]*P[i]+Q[i]+dp[j]

就是一条斜率为P[i]的直线,截(dis[j],dp[j])的最小截距

在根往下走的过程中,斜率单调递增

这就体现了 为什么题目中说“i号城市是j号城市的某个祖先,那么一定存在Pi<=Pj”

我们按dfs序dp

现在唯一的问题就是如何得到 一个点到根节点路径上的单调队列

只需要考虑如何去除兄弟节点的子树对单调队列的影响

即在一个节点退出dfs时,将单调队列恢复为这个节点开始dfs的情况

头指针只是不断的+1,没有涉及到单调队列中元素的修改,所以记录下头指针在哪个位置即可

尾指针涉及到元素的替换,但是它只会替换一个元素,所以记录下尾指针的位置,以及被当前点替换的元素是谁

当节点退出dfs时,恢复记录的这三个值即可

这样的话,一个节点多次出队入队,时间复杂度就不是O(n)了

所以二分出队位置,时间复杂度为O(nlogn)

 

朴素的DP:

#include<cstdio>
#include<iostream>
#include<algorithm>

using namespace std;

#define N 1000001

typedef long long LL;

int P[N],Q[N];

int front[N],to[N<<1],nxt[N<<1],val[N<<1],tot;

int fa[N];

LL dis[N];

int t;
LL mi[N];

void read(int &x)
{
    x=0; char c=getchar();
    while(!isdigit(c)) c=getchar();
    while(isdigit(c)) { x=x*10+c-'0'; c=getchar(); }
}

void add(int u,int v,int w)
{
    to[++tot]=v; nxt[tot]=front[u]; front[u]=tot; val[tot]=w;
    to[++tot]=u; nxt[tot]=front[v]; front[v]=tot; val[tot]=w;
}

void dfs(int x,int f)
{
    for(int i=front[x];i;i=nxt[i])
    {
        if(to[i]==f) continue;
        dis[to[i]]=dis[x]+val[i];
        mi[to[i]]=dis[to[i]]*P[to[i]]+Q[to[i]];
        t=fa[to[i]]; 
        while(t!=1)
        {
            mi[to[i]]=min(mi[to[i]],(dis[to[i]]-dis[t])*P[to[i]]+Q[to[i]]+mi[t]);
            t=fa[t];
        } 
        dfs(to[i],x);
    }
}

int main()
{
    int n,s;
    read(n);
    for(int i=1;i<n;++i)
    {
        read(fa[i+1]); read(s); read(P[i+1]); read(Q[i+1]);
        add(fa[i+1],i+1,s);
    }
    dfs(1,0);
    for(int i=2;i<=n;++i) cout<<mi[i]<<'\n';
}
View Code

相关文章:

  • 2021-09-30
  • 2021-11-21
  • 2021-11-28
  • 2021-11-29
  • 2021-07-26
  • 2021-10-16
  • 2021-09-11
猜你喜欢
  • 2021-06-16
  • 2022-02-22
  • 2022-12-23
  • 2022-01-15
  • 2021-10-24
  • 2021-08-14
  • 2022-01-10
相关资源
相似解决方案