[Bzoj1767][Ceoi2009]harbingers (树上斜率优化)

1767: [Ceoi2009]harbingers


 

Time Limit: 10 Sec  Memory Limit: 64 MB
Submit: 451  Solved: 120
[Submit][Status][Discuss]

Description


 

给定一颗树,树中每个结点有一个邮递员,每个邮递员要沿着唯一的路径走向capital(1号结点),每到一个城市他可以有两种选择: 1.继续走到下个城市 2.让这个城市的邮递员替他出发。 每个邮递员出发需要一个准备时间W[I],他们的速度是V[I],表示走一公里需要多少分钟。 现在要你求出每个城市的邮递员到capital的最少时间(不一定是他自己到capital,可以是别人帮他) N<=100000 3 ≤ N ≤ 100 000 0 ≤ Si≤ 10^9 1 ≤ Vi≤ 10^9 The length of each road will not exceed 10 000 For 20% of the tests, N ≤ 2 500 For 50% of the tests, each town will have at most 2 adjacent roads (i.e., the graph of roads will be a line)

Input


 

N 以下N-1行A,B,C三个数表示A,B之间有一条长为C的边。 再N行每行两数Wi,Vi

Output


 

  输出有一行N-1个数表示如题所述。

Sample Input


 

5 
1 2 20 
2 3 12 
2 4 1 
4 5 3 
26 9 
1 10 
500 2 
2 30

 

Sample Output


 

206 321 542 328

 

HINT


 

 

 

分析:


 

考虑暴力 

其中j为i的祖先。

发现很显然的斜率优化式子模型,转化成点斜式

发现x是单增的,可以使用单调栈维护下凸包,答案就是使截矩b最小的点对(x,y)

 

但发现斜率k是不单调的,代表决策不单调,所以只有采用二分寻找最优决策。

这还是很显然的,但是这道题是在树上,树上根节点到当前点的凸包要在它每个儿子都会使用到,要想办法维护根节点到每个点的凸包。

我们加入一个点后,不从队首开始pop,而是二分寻找它该加入的位置,把那个位置改成当前点,并记录下原来的点和位置。

处理它的所有儿子结点后,又把当前点改回原来的点。这样就做到保存各点凸包信息了。

坑点:会炸long long  比较斜率的时候被迫使用double。

AC代码:


 

# include <iostream>
# include <cstdio>
# include <cstring>
using namespace std;
const int N = 3e5 + 12;
typedef long long LL;
int n,dt,head[N],que[N];LL f[N],d[N],v[N],w[N];
struct Edge{
    int to,nex;LL w;
}edge[N << 1];
void AddEdge(int u,int v,LL w)
{
    edge[++dt] = (Edge){v,head[u],w};
    head[u] = dt;
}
LL x(int i){return d[i];}
LL y(int i){return f[i];}
LL Get(int A,int B){return f[A] + (d[B] - d[A]) * v[B] + w[B];}
double slope(int A,int B){return ((double)(y(B) - y(A))) / ((double)(x(B) - x(A)));}
bool Cross(int A,int B,int C){return slope(B,C) <= slope(A,B);}
int find(int x,int tp)
{
    int l = 2,r = tp,ret = 1,mid;
    while(l <= r)
    {
        mid = l + r >> 1;
        if(Get(que[mid],x) < Get(que[mid - 1],x))ret = mid,l = mid + 1;
        else r = mid - 1;
    }
    return ret;
}
int Find(int z,int tp)
{
    int l = 2,r = tp,ret = tp + 1,mid;
    while(l <= r)
    {
        mid = l + r >> 1;
        if(Cross(que[mid - 1],que[mid],z))ret = mid,r = mid - 1;
        else l = mid + 1;
    }
    return ret;
}
void dfs(int u,int pos,int fa)
{
    int qpos,qtop;
    f[u] = Get(que[find(u,pos)],u);
    qpos = Find(u,pos);qtop = que[qpos];
    que[qpos] = u;
    for(int i = head[u];i;i = edge[i].nex)
    {
        if(edge[i].to == fa)continue;
        d[edge[i].to] = d[u] + edge[i].w;
        dfs(edge[i].to,qpos,u);
    }
    que[qpos] = qtop;
}
int main(){
   scanf("%d",&n);
   int x,y,z;
   for(int i = 1;i < n;i++)
   {
       scanf("%d %d %d",&x,&y,&z);
       AddEdge(x,y,z);AddEdge(y,x,z);
   }
   for(int i = 2;i <= n;i++)scanf("%lld %lld",&w[i],&v[i]);
   dfs(1,0,0);printf("%lld",f[2]);
   for(int i = 3;i <= n;i++)printf(" %lld",f[i]);
}

 

 

 

 

 

转载于:https://www.cnblogs.com/lzdhydzzh/p/8622581.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值