凸性 DP 优化

首先这里点名 \(\rm WQS\) 二分还有决策单调性,但是今天就不写这个了,今天学了一些进阶的东西。

闵可夫斯基和

这个东西时是用来优化一类凸函数卷积的,一般就是背包或者分治时使用。最常用的是 \((\max / \min, +)\) 卷积。

首先考虑这个卷积式:\(f_k = \max_{i + j = k} \{ g_i + h_j \}\),暴力做卷积是 \(O(n^ 2)\) 的。

其中 \(g, h\) 具有凸性时,有以下性质:

性质 \(1\). \(f\) 也具有凸性。

考虑卷积的组合意义,假设我们将 \(g, h\) 分别差分得到 \(\Delta g, \Delta h\),每个数看做有相应价值的物品。那么 \(f_k\) 就相当于在两个数组中选出 \(k\) 个物品,且满足在 \(\Delta g, \Delta h\) 中都是一个前缀。但由于具有凸性,\(\Delta g, \Delta h\) 都是单调下降的,因此每次选的物品的价值都是单调下降的,即 \(\Delta f\) 是单调下降的。因此 \(f\) 具有凸性。这进一步给出了性质二。

性质 \(2\). \(\Delta f\)\(\Delta g, \Delta h\) 排序后一样

首先把 \(\Delta g, \Delta h\) 排序后得到一个数组 \(a\),因为 \(g, h\) 都有凸性,因此不难发现 \(a\) 中的某一个前缀都对应着 \(\Delta g, \Delta h\) 中的两个前缀的并集。因此,对于 \(f_i\),直接取 \(a\) 中最大的前 \(i\) 个数是合法的。同时,这个也是答案上界。

因此,我们有了第二个性质后,不难通过归并排序直接直接做到 \(O(n)\) 卷出 \(f\)。这个被我们成为闵可夫斯基和。

P9962 THUPC 2024 初赛 一棵树

首先考虑一些 \(naive\) 的东西:设 \(f_{u, i}\) 为在 \(u\) 子树内选了 \(i\) 个黑点的最小代价,当遇到一个新的子树就相当于做了一个背包合并。具体的,我们令 \(F_{u, i} = f_{u, i} + |2k - i|\),那么每次合并就相当于 \(F_v\)\(f_u\) 做了一次 \((\min, +)\) 卷积。那么由于 \(|2k - i|\) 是凸的,由归纳性可以知道 \(f, F\) 都是凸的。因此考虑闵可夫斯基和,维护 \(f\) 的查分数组,然后最后对于 \(u \ne 1\),相当于对一个前缀 \(-2\),一个后缀 \(+2\)。使用平衡树 + dsu on tree 即可做到 \(O(n \log^2 n)\)

细节很多,调了两百年。

qwq
#include<bits/stdc++.h>
#define ll long long
using namespace std;

mt19937 rnd(114514);
const int N = 5e5 + 10;

int n;
ll k;
struct edge{
    int v, next;
}edges[N << 1];
int head[N], idx;

void add_edge(int u, int v){
    edges[++idx] = {v, head[u]};
    head[u] = idx;
}

struct node{
    int ls, rs, siz;
    int key;
    ll tag, sum, val;
}tn[N * 2];
int tot, rub[N], rt[N], siz[N];
int newnode(ll val){
    int id = ((rub[0]) ? (rub[rub[0]--]) : (++tot));
    tn[id] = (node){0, 0, 1, (int)rnd(), 0, val, val};
    return id;
}

struct FHQ_treap{
    void pushup(int o){
        tn[o].sum = tn[tn[o].ls].sum + tn[tn[o].rs].sum + tn[o].val;
        tn[o].siz = tn[tn[o].ls].siz + tn[tn[o].rs].siz + 1;
    }
    void addtag(int o, ll v){tn[o].sum += 1ll * v * tn[o].siz; tn[o].tag += v; tn[o].val += v;}
    void pushdown(int o){
        if(!tn[o].tag) return;
        addtag(tn[o].ls, tn[o].tag); addtag(tn[o].rs, tn[o].tag);
        tn[o].tag = 0;
    }
    void split_val(int o, int val, int& x, int& y){
        if(!o){x = y = 0; return;}
        pushdown(o);
        if(tn[o].val <= val){
            x = o;
            split_val(tn[o].rs, val, tn[o].rs, y);
        } else {
            y = o;
            split_val(tn[o].ls, val, x, tn[o].ls);
        } pushup(o);
    }
    void split_siz(int o, int val, int& x, int& y){
        if(!o){x = y = 0; return;}
        pushdown(o);
        if(tn[tn[o].ls].siz + 1 <= val){
            x = o; val -= tn[tn[o].ls].siz + 1;
            split_siz(tn[o].rs, val, tn[o].rs, y);
        } else{
            y = o; 
            split_siz(tn[o].ls, val, x, tn[o].ls);
        } pushup(o);
    }
    int merge(int x, int y){
        if(!x || !y) return x + y;
        pushdown(x); pushdown(y);
        if(tn[x].key > tn[y].key){
            tn[x].rs = merge(tn[x].rs, y);
            pushup(x); return x;
        } else {
            tn[y].ls = merge(x, tn[y].ls);
            pushup(y); return y;
        }
    }
    void ins(int& o, int val){
        if(!o){o = newnode(val); return;}
        int x, y; split_val(o, val, x, y); 
        o = merge(merge(x, newnode(val)), y);
        return;
    }
    void clr(int& o){
        if(!o) return;
        pushdown(o);
        rub[++rub[0]] = o;
        clr(tn[o].ls); clr(tn[o].rs);
        o = 0;
    }
    void Segclr(int& o, int l){
        int x, y;
        split_siz(o, l, x, y);
        clr(y); o = x; 
    }
    void Segadd(int o, int l, int r, ll v){
        //cout << o << " " << l << " " << r << " " << v << "\n";
        if(l > r || l > tn[o].siz) return;
        int x, y, z, q; 
        split_siz(o, l - 1, x, y); split_siz(y, r, z, q);
        addtag(z, v); o = merge(merge(x, z), q);
    }
    void db(int o){
        if(!o) return;
        pushdown(o);
        db(tn[o].ls); cout << tn[o].val << " "; db(tn[o].rs);
    }
}tr;

void dfs(int u, int fa){
    int son = 0; siz[u] = 1;
    for(int i = head[u]; i; i = edges[i].next){
        int v = edges[i].v; if(v == fa) continue;
        dfs(v, u); int l = k / 2; siz[u] += siz[v];
        if(siz[v] > siz[son]) son = v;
        if(k & 1) tr.Segadd(rt[v], 1, min(siz[v], l), -2), tr.Segadd(rt[v], l + 2, siz[v], 2);
        else tr.Segadd(rt[v], 1, min(l, siz[v]), -2), tr.Segadd(rt[v], l + 1, siz[v], 2);
        //cout << v << " 2:" << "\n"; tr.db(rt[v]); cout << "\n";
    }
    rt[u] = rt[son]; tr.ins(rt[u], 0); tr.Segclr(rt[u], k);
    // cout << u << " 1:" << "\n";
    // tr.db(rt[u]); cout << "\n";
    for(int i = head[u]; i; i = edges[i].next){
        int v = edges[i].v; if(v == fa || v == son) continue;
        int now = rub[0]; tr.clr(rt[v]);
        for(int j = rub[0]; j > now; j--) tr.ins(rt[u], tn[rub[j]].val);
        tr.Segclr(rt[u], k);
    } tr.Segclr(rt[u], k);
    // cout << u << " 1:" << "\n";
    // tr.db(rt[u]); cout << "\n";
}

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    cin >> n >> k;
    for(int i = 1; i < n; i++){
        int x, y; cin >> x >> y;
        add_edge(x, y); add_edge(y, x);
    } dfs(1, 0);
    cout << tn[rt[1]].sum + 1ll * k * (n - 1);

    return 0;
}
posted @ 2025-01-21 20:59  Little_corn  阅读(46)  评论(0)    收藏  举报