Codeforces 1119FNiyaz and Small Degrees 树形DP+堆优化

Description

现在给你一颗树,边有边权,回答 n n n个询问,分别是对于 x = 0 , 1 , 2.. ( n − 1 ) x=0,1,2..(n−1) x=0,1,2..(n1),使得每个点的度数都不超过 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 &lt; = f [ y ] [ 0 ] f[y][1]+c&lt;=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]+cf[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 &gt; = i ] \sum_{i=1}^{n-1}\sum_{j=1}^n[deg_j&gt;=i] i=1n1j=1n[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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值