BZOJ1767/Gym207383I CEOI2009 Harbingers 斜率优化、可持久化单调栈、二分

传送门——BZOJCH

传送门——VJ

注:本题在BZOJ上是权限题,在Gym里面也不能直接看,所以只能在VJ上交了……


不难考虑到这是一个\(dp\)

\(dep_x\)表示\(x\)在树上的带权深度,\(parent_x\)表示\(x\)的祖先节点集合,\(f_x\)表示点\(x\)的答案

那么

\(f_x = \min\limits_{i \in parent_x}\{f_i + V_x \times (dep_x - dep_i)\} + S_x = \min\limits_{i \in parent_x}\{- dep_i \times V_x + f_i\} + S_x + V_x \times dep_x\)

这是一个典型的斜率优化模型,斜率为\(-dep_i\),截距为\(f_i\),自变量为\(V_x\)

首先对于树上的一条链,\(V_x\)并不单调,所以我们不能使用单调队列。但是因为边权非负,所以斜率单调不降,也就意味着我们不需要使用平衡树,而只需要使用单调栈+二分就可以解决维护凸包的问题。

再者,因为不是序列上的问题,那么计算到某一个点后,当前的单调栈会被其所有儿子共用,而儿子不止一个,所以我们需要将单调栈持久化以应对多次的使用。使用可持久化线段树解决这个问题。

最后注意:在维护凸包时不能暴力\(pop\)当前不满足条件的直线,而是应该在单调栈上二分找到最后一个在凸包上的直线。这样才能够保证复杂度。

总复杂度为\(O(nlog^2n)\)

下面是一份常数巨大的代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<ctime>
#include<cctype>
#include<algorithm>
#include<cstring>
#include<iomanip>
#include<queue>
#include<map>
#include<set>
#include<bitset>
#include<stack>
#include<vector>
#include<cmath>
#define int long long
#define mid ((l + r) >> 1)
#define lch Tree[x].l
#define rch Tree[x].r
//This code is written by Itst
using namespace std;

inline int read(){
    int a = 0;
    char c = getchar();
    bool f = 0;
    while(!isdigit(c) && c != EOF){
        if(c == '-')
            f = 1;
        c = getchar();
    }
    if(c == EOF)
        exit(0);
    while(isdigit(c)){
        a = a * 10 + c - 48;
        c = getchar();
    }
    return f ? -a : a;
}

const int MAXN = 1e5 + 7;
struct Edge{
    int end , upEd , w;
}Ed[MAXN << 1];
struct line{
    int k , b;
    line(int _k = 0 , int _b = 0):k(_k) , b(_b){}
};
struct node{
    int l , r;
    line L;
}Tree[MAXN * 30];
int rt[MAXN] , head[MAXN] , ans[MAXN] , dep[MAXN] , V[MAXN] , S[MAXN] , cnt[MAXN];
int cntN , cntEd , N , maxN;

inline void addEd(int a , int b , int c){
    Ed[++cntEd].end = b;
    Ed[cntEd].upEd = head[a];
    Ed[cntEd].w = c;
    head[a] = cntEd;
}

inline int calc(line l , int x){
    return l.k * x + l.b;
}

line getL(int x , int l , int r , int tar){
    if(l == r)
        return Tree[x].L;
    if(mid >= tar)
        return getL(lch , l , mid , tar);
    return getL(rch , mid + 1 , r , tar);
}

void ins(int &x , int l , int r , int tar , line L){
    int t = ++cntN;
    Tree[t] = Tree[x];
    x = t;
    if(l == r){
        Tree[t].L = L;
        return;
    }
    if(mid >= tar)
        ins(lch , l , mid , tar , L);
    else
        ins(rch , mid + 1 , r , tar , L);
}

inline int erf(int id , int x){
    int L = 1 , R = cnt[id];
    while(L < R){
        int MID = (L + R) >> 1;
        if(calc(getL(rt[id] , 1 , N , MID) , x) > calc(getL(rt[id] , 1 , N , MID + 1) , x))
            L = MID + 1;
        else
            R = MID;
    }
    return calc(getL(rt[id] , 1 , N , L) , x);
}

inline bool ifpop(line L1 , line L2 , line L3){
    return (long double)(L1.b - L2.b) * (L3.k - L1.k) >= (long double)(L1.b - L3.b) * (L2.k - L1.k);
}

inline int erf2(int id , line l){
    int L = 1 , R = cnt[id];
    while(L < R){
        int MID = (L + R) >> 1;
        if(ifpop(getL(rt[id] , 1 , N , MID) , getL(rt[id] , 1 , N , MID + 1) , l))
            R = MID;
        else
            L = MID + 1;
    }
    return L;
}

void dfs(int x , int p){
    bool f = 1;
    if(x != 1){
        rt[x] = rt[p];
        cnt[x] = cnt[p];
        ans[x] = erf(x , V[x]) + dep[x] * V[x] + S[x];
        line p = getL(rt[x] , 1 , N , cnt[x]);
        if(p.k == -dep[x])
            if(p.b > ans[x])
                --cnt[x];
            else
                f = 0;
        if(f)
            cnt[x] = erf2(x , line(-dep[x] , ans[x]));
    }
    if(f)
        ins(rt[x] , 1 , N , ++cnt[x] , line(-dep[x] , ans[x]));
    for(int i = head[x] ; i ; i = Ed[i].upEd)
        if(Ed[i].end != p){
            dep[Ed[i].end] = dep[x] + Ed[i].w;
            dfs(Ed[i].end , x);
        }
}

signed main(){
#ifndef ONLINE_JUDGE
    freopen("in","r",stdin);
    freopen("out","w",stdout);
#endif
    N = read();
    for(int i = 1 ; i < N ; ++i){
        int a = read() , b = read() , c = read();
        addEd(a , b , c);
        addEd(b , a , c);
    }
    for(int i = 2 ; i <= N ; ++i){
        S[i] = read();
        V[i] = read();
    }
    dfs(1 , 0);
    for(int i = 2 ; i <= N ; ++i)
        cout << ans[i] << ' ';
    return 0;
}

转载于:https://www.cnblogs.com/Itst/p/10326921.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值