zoukankan      html  css  js  c++  java
  • BZOJ1767 [CEOI2009]harbingers

    题意简述

    给定一颗树 ( (N) 个结点) , 树中每个结点有一个邮递员, 每个邮递员要沿着唯一的路径走向 (Capital) ( (1) 号结点 ) , 每到一个城市他可以有两种选择 :

    1. 继续走到下个城市.
    2. 让这个城市的邮递员替他出发.每个邮递员出发需要一个准备时间 (S[i]) , 他们的速度是 (V_i) ,表示走一公里需要多少分钟.

    现在要你求出每个城市的邮递员到 (Capital) 的最少时间 ( 不一定是他自己到 (Capital) , 可以是别人帮他 )

    数据范围: (N leq 10^5)

    题目分析

    (1) 号结点看作根, 题目相当于求每个结点到根的最短用时.

    考虑 动态规划.

    1. 状态设计: 设 (F[i]) 表示从 (i) 号结点出发到根的最短用时.

    2. 状态转移方程:

      (F[i] = min (F[j] + S[i] + (Dis[i] - Dis[j]) * V[i]))

      解释: (Dis[i]) 表示 (i) 到根结点的距离, (j)(i) 的祖先结点.

    这个暴力转移的时间复杂度是 (O (n^2)) 的, 直接爆炸. 于是考虑优化.

    开始转化方程:

    首先把 (min) 忽略掉 :

    (F[i] = F[j] + S[i] + (Dis[i] - Dis[j]) * V[i])

    光翼展开 :

    (F[i] = F[j] + S[i] + Dis[i] * V[i] - Dis[j] * V[i])

    斗转星移项 :

    (F[j] = V[i] * Dis[j] + F[i] - Dis[i] * V[i] - S[i])

    斜率优化 的形式 !

    把上式看作一条直线的解析式 :

    (F[j])(Dis[j]) 一起看作二维平面中的点 ((Dis[j], F[j])) ;

    (V[i]) 看作斜率 ;

    (F[i] - Dis[i] * V[i] - S[i]) 看作纵截距 (即直线与 (y) 轴交点的纵坐标) ;

    转移就等价于最小化纵截距! 这个可以通过维护下凸壳 + 二分解决.

    "能单调队列吗?"

    "不能!"

    因为 (V[i]) 不具有单调性, 本题中最优决策点 不具有单调性.

    这直接使得单调队列不能随意弹出队头决策点. 不能弹出队头自然就想到了 单调栈.

    具体做法如下 :

    1. (DFS)
    2. 遇到一个结点
    3. 更新其答案
    4. 将其加入单调栈
    5. 回溯

    发现算法实现的问题在于单调栈在 (DFS) 过程中的维护.

    如果每次暴力的弹栈, 暴力的回溯恢复栈, 时间会直接爆炸.

    不妨这样做 :

    1. 二分找到 决策点 (i) 的插入位置 (k + 1).
    2. 记录当前栈大小 (Top), 及 (Sta[k + 1]) 的值.
    3. (Top) 赋值为 (k + 1).
    4. 回溯时只需恢复 (Top)(Sta[k + 1]) 的值.

    这相当于 假装 进行了弹栈操作, 从而保证每次只会修改一个位置的值, 便于快速还原.

    再来考虑如何二分找到最优决策点 :

    int Zoe (int p) {
    	int L, R, Mid;
    	L = 1, R = Top;
    	while (L <= R) {
    		Mid = (L + R) >> 1;
    		if (Slope (Sta[Mid - 1], Sta[Mid]) > K (p)) R = Mid - 1;
    		else if (Slope (Sta[Mid], Sta[Mid + 1]) < K (p)) L = Mid + 1;
    		else return Mid;
    	} return Mid;
    }
    

    (Sta[]) 为单调栈, (Slope) 求两点斜率, (K (p)) 即为 点 (p) 斜率 (V[p])

    如图所示, 此时最优决策点应在 ([L, Mid - 1]) 中.

    如图所示, 此时最优决策点应在 ([Mid + 1, R]) 中.

    如图所示, 此时最优决策点就为 (Sta[Mid]).

    再考虑如何二分找到当前点的插入位置.

    由于本题中一条路径上的 (Dis) 是单调不降的, 在图像上的体现为决策点集 (x) 坐标的不降.

    int Nico (int p) {
    	int L, R, Mid;
    	L = 1, R = Top;
    	while (L <= R) {
    		Mid = (L + R) >> 1;
    		if (Slope (Sta[Mid], Sta[Mid + 1]) < Slope (Sta[Mid], p)) L = Mid + 1;
    		else if (Slope (Sta[Mid - 1], Sta[Mid]) > Slope (Sta[Mid - 1], p)) R = Mid - 1;
    		else return Mid;
    	} return Mid;
    }
    

    如图所示, 此时 (p) 点弹不掉 (Sta[Mid + 1]) , 插入位置应更加靠后, 即 ([Mid + 1, R])

    如图所示, 此时 (p) 点能弹掉 (Sta[Mid]) , 插入位置应更加靠前, 即 ([L, Mid - 1])

    如图所示, 此时 (p) 点能弹掉 (Sta[Mid + 1]), 但弹不掉 (Sta[Mid]), 于是位置即为 (Mid + 1)

    剩下的就没有问题了吧...

    没有了吧...

    有了吧...

    了吧...

    吧...

    代码实现

    #include <cstdio>
    
    typedef long long LL;
    
    const int N = 1e5;
    
    int n;
    LL S[N + 5], V[N + 5];
    
    int Head[N + 5], Cnt;
    struct Edge { int Nxt, To; LL Val; } E[N * 2 + 5];
    
    LL F[N + 5], Dis[N + 5];
    
    int Sta[N + 5], Top;
    
    LL X (int);
    LL Y (int);
    LL K (int);
    double Slope (int, int);
    void Add (int, int, LL);
    void DFS (int, int);
    int Zoe (int);
    int Nico (int);
    
    int main() {
    	scanf ("%d", &n);
    	for (int i = 1; i <= n - 1; ++i) {
    		int u, v, w;
    		scanf ("%d%d%d", &u, &v, &w);
    		Add (u, v, 1ll * w), Add (v, u, 1ll * w);
    	} for (int i = 2; i <= n; ++i) scanf ("%lld%lld", &S[i], &V[i]);
    	
    	DFS (1, 0);
    	for (int i = 2; i <= n; ++i) printf ("%lld ", F[i]);
    	return 0;
    
    } LL X (int p) { return Dis[p];
    
    } LL Y (int p) { return F[p];
    
    } LL K (int p) { return V[p];
    
    } double Slope (int u, int v) {
    	if (X (u) == X (v)) return 0;
    	return (double) (Y (u) - Y (v)) / (X (u) - X (v));
    
    } void Add (int u, int v, LL w) {
    	E[++Cnt] = (Edge) { Head[u], v, w }, Head[u] = Cnt;
    
    } int Zoe (int p) {
    	int L, R, Mid;
    	L = 1, R = Top;
    	while (L <= R) {
    		Mid = (L + R) >> 1;
    		if (Slope (Sta[Mid - 1], Sta[Mid]) > K (p)) R = Mid - 1;
    		else if (Slope (Sta[Mid], Sta[Mid + 1]) < K (p)) L = Mid + 1;
    		else return Mid;
    	} return Mid;
    
    } int Nico (int p) {
    	int L, R, Mid;
    	L = 1, R = Top;
    	while (L <= R) {
    		Mid = (L + R) >> 1;
    		if (Slope (Sta[Mid], Sta[Mid + 1]) < Slope (Sta[Mid], p)) L = Mid + 1;
    		else if (Slope (Sta[Mid - 1], Sta[Mid]) > Slope (Sta[Mid - 1], p)) R = Mid - 1;
    		else return Mid;
    	} return Mid;
    
    } void DFS (int p, int Fp) {
    	int tap = Zoe (p);
    	F[p] = F[Sta[tap]] + S[p] + (Dis[p] - Dis[Sta[tap]]) * V[p];
    	
    	int tbp, ReTop, ReVal;
    	tbp = Nico (p);
    	ReTop = Top, ReVal = Sta[tbp + 1];
    	Sta[Top = tbp + 1] = p;
    	for (int i = Head[p]; i; i = E[i].Nxt) {
    		int v = E[i].To;
    		if (v == Fp) continue;
    		Dis[v] = Dis[p] + E[i].Val, DFS (v, p);
    	} Top = ReTop, Sta[tbp + 1] = ReVal;
    }
    
  • 相关阅读:
    感觉博客又要停一停了
    dockManager 添加DockPanel控件
    C# 线性渐变圆
    【Axure】母版引发事件
    【系统问题】windows10打印就蓝屏-报错误代码“win32kfull.sys”
    论扇形的绘制方式
    字符串可以使用substring等方法的原因解析
    深入理解作用域链
    实现fn(1,2)(3)(4).getSum(),使得最后输出值为实参的和即10
    函数里面for循环延迟打印引发的闭包问题
  • 原文地址:https://www.cnblogs.com/Rothen/p/13956188.html
Copyright © 2011-2022 走看看