• CF932F Escape Through Leaf 斜率优化、启发式合并


    传送门


    (DP)

    (f_i)表示第(i)个节点的答案,(S_i)表示(i)的子节点集合,那么转移方程为(f_i = minlimits_{j in S_i} {a_i imes b_j + f_j})

    这是一个很明显的斜率优化式子,斜率为(b_j),截距为(f_j),自变量为(a_i)。考虑到斜率没有单调性,所以使用set维护凸包。

    使用set维护凸包比较简单。一条直线插入时,先判断这条线段在当前凸包中是否合法,然后不断把两边不合法的直线删去。具体的实现看下面代码的insert函数吧

    然后如何将一个点所有儿子的set合并为自己的set呢?使用启发式合并来保证复杂度。

    最后,知道了当前点的所有儿子节点的凸包,如何计算当前点的答案?正解是二分斜率,因为你没法在set上直接二分直线。

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

    还可以通过dfn序将树上问题转化为序列问题,对一个点有贡献的是一段连续区间,就可以用CDQ分治解决。

    #include<bits/stdc++.h>
    #define int long long
    #define ld long double
    //This code is written by Itst
    using namespace std;
    
    inline int read(){
        int a = 0;
        bool f = 0;
        char c = getchar();
        while(c != EOF && !isdigit(c)){
            if(c == '-')
                f = 1;
            c = getchar();
        }
        while(c != EOF && isdigit(c)){
            a = (a << 3) + (a << 1) + (c ^ '0');
            c = getchar();
        }
        return f ? -a : a;
    }
    
    const int MAXN = 100010;
    struct Edge{
        int end , upEd;
    }Ed[MAXN << 1];
    struct node{
        int k , b;
    }now;
    set < node > s[MAXN];
    long long sum[MAXN] , a[MAXN] , b[MAXN] , minN[MAXN] ,  fa[MAXN] , size[MAXN] , head[MAXN] , N , cntEd;
    
    bool operator <(node a , node b){
        return a.k < b.k || (a.k == b.k && a.b < b.b);
    }
    
    inline void addEd(int a , int b){
        Ed[++cntEd].end = b;
        Ed[cntEd].upEd = head[a];
        head[a] = cntEd;
    }
    
    void dfs(int now , int f){
        fa[now] = f;
        size[now] = 1;
        for(int i = head[now] ; i ; i = Ed[i].upEd)
            if(Ed[i].end != f){
                dfs(Ed[i].end , now);
                size[now] += size[Ed[i].end];
            }
        if(size[now] == 1)
            minN[now] = 0;
    }
    
    inline ld calcNode(node a , node b){
        return (a.b - b.b) / (ld)(b.k - a.k);
    }
    
    inline void insert(int now , int k , int b){//插入一条斜率为k、截距为b的直线
        node x , l , r;
        x.k = k;
        x.b = b;
        set < node > :: iterator it = s[now].lower_bound(x);
        if(it != s[now].end() && (*it).k == k){//判断是否存在斜率相同的直线
            s[now].erase(it);
            it = s[now].lower_bound(x);
        }
        else
            if(it != s[now].begin() && (*--it).k == k)
                return;
        it = s[now].lower_bound(x);
        if(it != s[now].begin() && it != s[now].end()){
            l = *it , r = *(--it);
            if(calcNode(r , x) < calcNode(l , r) && calcNode(l , r) < calcNode(l , x))//判断这一条直线是否能够被加入当前凸包
                return;
            ++it;
        }
        while(1){//向两边删去不合法直线
            it = s[now].lower_bound(x);
            if(it == s[now].end())
                break;
            l = *it;
            if(++it == s[now].end())
                break;
            r = *it;
            if(calcNode(l , r) > calcNode(l , x) && calcNode(l , r) > calcNode(r , x))
                s[now].erase(--it);
            else
                break;
        }	
        while(1){
            it = s[now].lower_bound(x);
            if(it == s[now].begin())
                break;
            l = *(--it);
            if(it == s[now].begin())
                break;
            r = *(--it);
            if(calcNode(l , r) < calcNode(l , x) && calcNode(l , r) < calcNode(r , x))
                s[now].erase(++it);
            else
                break;
        }
        s[now].insert(x);
    }
    
    inline void merge(int root , int now){
        bool f = 0;
        if(size[root] < size[now]){
            swap(root , now);
            f = 1;
        }
        for(set < node > :: iterator it = s[now].begin() ; it != s[now].end() ; ++it)
            insert(root , (*it).k , (*it).b);
        if(f){
            s[now] = s[root];
            swap(now , root);
        }
        s[now].clear();
    }
    
    void dsu(int now){//别被误导了,这个不是dsu on tree
        if(size[now] == 1){
            insert(now , b[now] , minN[now]);
            return;
        }
        for(int i = head[now] ; i ; i = Ed[i].upEd)
            if(Ed[i].end != fa[now]){
                dsu(Ed[i].end);
                merge(now , Ed[i].end);
            }
        int l = -1e5 - 1 , r = 1e5 + 1;
        set < node > :: iterator it;
        node L , R;
        while(l < r){
            int mid = l + r >> 1;
            it = s[now].lower_bound((node){mid , (long long)-1e15});
            if(it == s[now].begin()){
                l = mid + 1;
                continue;
            }
            if(it == s[now].end()){
                r = mid;
                continue;
            }
            L = *it;
            R = *(--it);
            if(R.k * a[now] + R.b >= L.k * a[now] + L.b)
                l = mid + 1;
            else
                r = mid;
        }
        L = *s[now].lower_bound((node){l - 1 , (long long)-1e15});
        minN[now] = L.k * a[now] + L.b;
        insert(now , b[now] , minN[now]);
    }
    
    signed main(){
        N = read();
        memset(minN , 0x3f , sizeof(minN));
        for(int i = 1 ; i <= N ; i++)
            a[i] = read();
        for(int i = 1 ; i <= N ; i++)
            b[i] = read();
        for(int i = 1 ; i < N ; i++){
            int a = read() , b = read();
            addEd(a , b);
            addEd(b , a);
        }
        dfs(1 , 0);
        dsu(1);
        for(int i = 1 ; i <= N ; i++)
            cout << minN[i] << ' ';
        return 0;
    }
    
  • 相关阅读:
    限制泛型可用类型,类型通配符声明,泛型方法
    泛型简介,泛型类及使用
    异常概念和处理机制,try-catch-finally,throw和throws,自定义异常
    随机验证码
    常用类--Date日期类,SimpleDateFormat日期格式类,Calendar日历类,Math数学工具类,Random随机数类
    String、StringBuffer和StringBuilder,定义一个自己的StringBuilder的类
    自动装箱和拆箱,枚举类型
    使用内部类开发一个存放数据的容器
    手推期望、方差
    ML 徒手系列 最大似然估计
  • 原文地址:https://www.cnblogs.com/Itst/p/10328668.html
Copyright © 2020-2023  润新知