凸性 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  阅读(106)  评论(0)    收藏  举报