Description
现在给你一颗树,边有边权,回答 n n n个询问,分别是对于 x = 0 , 1 , 2.. ( n − 1 ) x=0,1,2..(n−1) x=0,1,2..(n−1),使得每个点的度数都不超过 x x x,最小化删掉的权值。
Sample Input
5
1 2 1
1 3 2
1 4 3
1 5 4
Sample Output
10 6 3 1 0
先来考虑一个给定一个
x
x
x的做法。
设
f
[
x
]
[
0
]
f[x][0]
f[x][0]为不考虑他与他父亲那条边把一个点删到度数小于等于
x
x
x且子树内合法的最小答案。
设
f
[
x
]
[
1
]
f[x][1]
f[x][1]为不考虑他与他父亲那条边把一个点删到度数小于等于
x
+
1
x+1
x+1且子树内合法的最小答案。
那么假设转移的时候对于一个
y
y
y满足
f
[
y
]
[
1
]
+
c
<
=
f
[
y
]
[
0
]
f[y][1]+c<=f[y][0]
f[y][1]+c<=f[y][0],那么这个边显然可以直接删掉。
否则的话我们对每个点开一个堆,维护一个
f
[
y
]
[
1
]
+
c
−
f
[
y
]
[
0
]
f[y][1]+c-f[y][0]
f[y][1]+c−f[y][0]的值,取前若干个即可。
考虑优化这个
d
p
dp
dp。
我们从小到大枚举
x
x
x,
对于一个
x
x
x显然只有度数大于
x
x
x的需要处理,我们把度数大于
x
x
x的去做树形
d
p
dp
dp,度数小于等于
x
x
x的我们已经可以把他扔到与他相邻的点的堆里面。
每次对于大于
x
x
x的点做上面的
d
p
dp
dp。
但这时会出现一个问题,对于度数小于等于
x
x
x的点我们把他扔进堆里面但做完一遍
d
p
dp
dp有可能就被弹出来了,于是我们要把他加回去。
每次做
d
p
dp
dp的时候先把堆中的元素保持在你需要的范围内。
这样子的话弹出来的点的个数是不会超过块的大小的。
考虑树形
d
p
dp
dp的复杂度,考虑每一个
x
x
x的复杂度可得:
∑
i
=
1
n
−
1
∑
j
=
1
n
[
d
e
g
j
>
=
i
]
\sum_{i=1}^{n-1}\sum_{j=1}^n[deg_j>=i]
i=1∑n−1j=1∑n[degj>=i]
那么就是每个点度数的总和,那样的话复杂度是
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)的。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int _max(int x, int y) {return x > y ? x : y;}
int _min(int x, int y) {return x < y ? x : y;}
const int N = 250001;
int read() {
int s = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
return s * f;
}
void put(LL x) {
if(x >= 10) put(x / 10);
putchar(x % 10 + '0');
}
struct que {
priority_queue<LL> A, B;
void push(LL x) {A.push(x);}
void del(LL x) {B.push(x);}
LL top() {while(B.size() && B.top() == A.top()) A.pop(), B.pop(); return A.top();}
void pop() {A.pop();}
int size() {return (int)A.size() - (int)B.size();}
} z[N];
//支持删除和插入的堆
struct node {int y, c;};
int now, du[N], nxt[N];
LL sum[N], f[N][2];
vector<node> q[N];
vector<int> g[N];
bool v[N];
bool cmp(node a, node b) {return du[a.y] > du[b.y];}
void ins(int x, int y, int c) {
q[x].push_back(node{y, c});
}
void push(int x) {
for(int k = 0; k < q[x].size(); k++) {
int y = q[x][k].y, c = q[x][k].c;
if(du[y] <= now) break;
z[y].push(c), sum[y] += c;
}
}
void pop(int x, int c) {
while(z[x].size() > c) {
sum[x] -= z[x].top();
z[x].pop();
}
}
void pop1(int x, int c, vector<LL> &add) {
while(z[x].size() > c) {
int s = z[x].top();
sum[x] -= s;
add.push_back(s);
z[x].pop();
}
}
void treedp(int x) {
v[x] = 1; int ned = du[x] - now;
pop(x, ned); LL tot = 0;
vector<LL> del, add; del.clear(), add.clear();
for(int k = 0; k < q[x].size(); k++) {
int y = q[x][k].y, c = q[x][k].c;
if(!v[y]) {
if(du[y] <= now) break;
treedp(y);
if(f[y][1] + c <= f[y][0]) {ned--; tot += f[y][1] + c;}
else {
tot += f[y][0];
LL o = f[y][1] + c - f[y][0];
del.push_back(o), z[x].push(o);
sum[x] += o;
}
}
} pop1(x, _max(0, ned), add);
f[x][0] = tot + sum[x];
pop1(x, _max(0, ned - 1), add);
f[x][1] = tot + sum[x];
//把该加的加回来,同时把当前dp加入的节点删走
for(int j = 0; j < add.size(); j++) z[x].push(add[j]), sum[x] += add[j];
for(int j = 0; j < del.size(); j++) z[x].del(del[j]), sum[x] -= del[j];
}
int main() {
int n = read(); LL ans = 0;
for(int i = 1; i < n; i++) {
int x = read(), y = read(), c = read();
ins(x, y, c), ins(y, x, c), ans += c;
du[x]++, du[y]++;
} for(int i = 1; i <= n; i++) sort(q[i].begin(), q[i].end(), cmp), g[du[i]].push_back(i);
nxt[n - 1] = n;
for(int i = n - 2; i >= 1; i--) {
if((int)g[i + 1].size()) nxt[i] = i + 1;
else nxt[i] = nxt[i + 1];
} put(ans), putchar(' ');
for(int i = 1; i < n; i++) {
ans = 0; now = i;
for(int j = 0; j < g[i].size(); j++) push(g[i][j]);
for(int j = nxt[i]; j < n; j = nxt[j]) {
for(int k = 0; k < g[j].size(); k++) {
int x = g[j][k];
if(v[x]) continue;
treedp(x), ans += f[x][0];
}
} for(int j = nxt[i]; j < n; j = nxt[j]) for(int k = 0; k < g[j].size(); k++) v[g[j][k]] = 0;
put(ans), putchar(' ');
} return 0;
}