凸性 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;
}