快来踩爆这个蒟蒻吧|

Little_corn

园龄:1年1个月粉丝:11关注:17

2025-01-21 20:59阅读: 26评论: 0推荐: 0

凸性 DP 优化

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

闵可夫斯基和

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

首先考虑这个卷积式:fk=maxi+j=k{gi+hj},暴力做卷积是 O(n2) 的。

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

性质 1. f 也具有凸性。

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

性质 2. ΔfΔg,Δh 排序后一样

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

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

P9962 THUPC 2024 初赛 一棵树

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

细节很多,调了两百年。

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

本文作者:Little_corn

本文链接:https://www.cnblogs.com/little-corn/p/18684410

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Little_corn  阅读(26)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起