[树形dp]Weight the Tree Codeforces1646D

You are given a tree of nn vertices numbered from 11 to nn. A tree is a connected undirected graph without cycles.

For each i=1,2,…,ni=1,2,…,n, let wiwi be the weight of the ii-th vertex. A vertex is called good if its weight is equal to the sum of the weights of all its neighbors.

Initially, the weights of all nodes are unassigned. Assign positive integer weights to each vertex of the tree, such that the number of good vertices in the tree is maximized. If there are multiple ways to do it, you have to find one that minimizes the sum of weights of all vertices in the tree.

Input

The first line contains one integer nn (2≤n≤2⋅1052≤n≤2⋅105) — the number of vertices in the tree.

Then, n−1n−1 lines follow. Each of them contains two integers uu and vv (1≤u,v≤n1≤u,v≤n) denoting an edge between vertices uu and vv. It is guaranteed that the edges form a tree.

Output

In the first line print two integers  — the maximum number of good vertices and the minimum possible sum of weights for that maximum.

In the second line print nn integers w1,w2,…,wnw1,w2,…,wn (1≤wi≤1091≤wi≤109)  — the corresponding weight assigned to each vertex. It can be proven that there exists an optimal solution satisfying these constraints.

If there are multiple optimal solutions, you may print any.

Examples

input

4
1 2
2 3
2 4

output

3 4
1 1 1 1 

input

3
1 2
1 3

output

2 3
1 1 1 

input

2
1 2

output

2 2
1 1

input

9
3 4
7 6
2 1
8 3
5 6
1 8
8 6
9 6

output

6 11
1 1 1 1 1 1 1 3 1 

题意: 给出一颗树,现在需要你为每个点分配权值,当一个点权值等于其相邻点权值和,那么该点被称为good点,求在满足good点最大的前提下树上所有点权和最小的方案。

分析: 首先需要想到相邻两点不可能同时被记为good点,这样这道题目就有点像经典树形dp题——没有上司的舞会了,设状态dp[i][0/1]表示在以i为根的子树中且i点不是good点/i点是good点的最大总good点数,为了统计最终方案还需要记录dp2[i][0/1],dp2[i][0/1]表示在以i为根的子树中且i点不是good点/i点是good点的总权重最小值。

接下来考虑状态转移,如果当前点是good点那么所有子节点都不能是good点,所以dp[now][1] += dp[son][0],dp2[now][1] += dp2[son][0],如果当前点不是good点那么子结点可以是good点也可以不是good点,具体哪种情况取决于dp值和dp2值,如果dp[son][0] > dp[son][1],那么dp[now][0] += dp[son][0],dp2[now][0] += dp2[son][0],如果dp[son][0] < dp[son][1],那么dp[now][0] += dp[son][1],dp2[now][0] += dp2[son][1],如果dp[son][0] == dp[son][1],此时dp[now][0] += dp[son][0],毕竟都一样大,加哪个都一样,不过这时候dp2[now][0]就需要加小的那个了,在这种情况下如果dp2[son][0] < dp2[son][1],那么dp2[now][0] += dp2[son][0],否则dp2[now][0] += dp2[son][1]。

状态初始化就是对于叶子结点now,令dp[now][1] = 1, dp[now][0] = 0, dp2[now][1] = (点now相邻点个数), dp2[now][0] = 0,然后先dfs到树的最底层,回溯的过程中用子结点信息更新当前点信息。

求出dp数组和dp2数组后就可以开始构造方案了,这个过程其实和状态转移的思想类似。设一个布尔数组st[i][0/1],st[i][0]表示在根节点不是good点的情况下i号点是否为good点,st[i][1]表示在根节点是good点的情况下i号点是否为good点。根据根节点是否为good点分为两种情况,这两种情况下的最优方案都需要求解。具体过程其实也是一个dfs,以根节点为good点这种情况举例,此时先初始化st[1][1] = true,然后进入dfs,对于每个点都需要看其父节点的状态,如果st[fa][1] == true,那么当前点状态直接已知,肯定是st[now][1] = false,如果st[fa][1] = false,那么当前点可good也可不good,如果dp[now][1] > dp[now][0],那么st[now][1]就应该取good点更优,st[now][1] = true,如果dp[now][1] < dp[now][0],那么st[now][1]不取good点更优,st[now][1] = false,如果dp[now][1] == dp[now][0],再看dp2[now][1]和dp2[now][0],如果dp2[now][1] < dp2[now][0],st[now][1] = true,否则st[now][1] = false,对于根节点不为good点的情况也是一样的,只不过初始化st[1][0] = false。

最后特判一下n == 2的情况,因此此时不符合前面说的相邻两点间只有一个点能是good点。

具体代码如下:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string>
#include <cstring>
#include <vector>
#define int long long
using namespace std;

int dp[200005][2], s[2], dp2[200005][2]; 
bool st[200005][2];
vector<int> to[200005];

void dfs(int now, int fa){
	for(int i = 0; i < to[now].size(); i++){
		int v = to[now][i];
		if(v == fa) continue;
		dfs(v, now);
	}
	dp[now][1] = 1, dp[now][0] = 0;
	dp2[now][1] = to[now].size(), dp2[now][0] = 0;
	int mx = 0;
	for(int i = 0; i < to[now].size(); i++){
		int v = to[now][i];
		if(v == fa) continue;
		if(dp[v][1] > dp[v][0]){
			mx += dp[v][1];
			dp2[now][0] += dp2[v][1];
		}
		else if(dp[v][0] > dp[v][1]){
			mx += dp[v][0];
			dp2[now][0] += dp2[v][0];
		}
		else{
			mx += dp[v][0];
			dp2[now][0] += min(dp2[v][0], dp2[v][1]);
		}
		dp[now][1] += dp[v][0];
		dp2[now][1] += dp2[v][0];
	}
	dp[now][0] += mx;
}

void dfs2(int now, int fa, int type){
	if(fa != 0){
		if(st[fa][type] == 1) st[now][type] = 0;
		else if(dp[now][0] > dp[now][1])
			st[now][type] = 0;
		else if(dp[now][1] > dp[now][0])
			st[now][type] = 1;
		else if(dp2[now][0] > dp2[now][1])
			st[now][type] = 1; 
		else
			st[now][type] = 0;
	}
	for(int i = 0; i < to[now].size(); i++){
		int v = to[now][i];
		if(v == fa) continue;
		dfs2(v, now, type);
	}
}

signed main()
{
	int n;
	cin >> n;
	for(int i = 1; i < n; i++){
		int u, v;
		scanf("%lld%lld", &u, &v);
		to[u].push_back(v);
		to[v].push_back(u);
	}
	if(n == 2){
		puts("2 2\n1 1");
		return 0;
	}
	dfs(1, 0);
	printf("%lld ", max(dp[1][0], dp[1][1]));
	s[0] = s[1] = 0;
	//1无贡献时权值和 
	st[1][0] = 0;
	dfs2(1, 0, 0);
	//1有贡献时权值和
	st[1][1] = 1;
	dfs2(1, 0, 1);
	for(int i = 1; i <= n; i++){
		if(st[i][0]) s[0] += to[i].size();
		else s[0]++;
	}
	for(int i = 1; i <= n; i++){
		if(st[i][1]) s[1] += to[i].size();
		else s[1]++;				
	}
	if(dp[1][0] > dp[1][1]){
		printf("%lld\n", s[0]);
		for(int i = 1; i <= n; i++){
			if(st[i][0]) printf("%lld ", to[i].size());
			else printf("1 ");
		}
	}
	else if(dp[1][0] < dp[1][1]){
		printf("%lld\n", s[1]);
		for(int i = 1; i <= n; i++){
			if(st[i][1]) printf("%lld ", to[i].size());
			else printf("1 ");
		}
	}
	else{
		printf("%lld\n", min(s[0], s[1]));
		if(s[0] < s[1]){
			for(int i = 1; i <= n; i++){
				if(st[i][0]) printf("%lld ", to[i].size());
				else printf("1 ");
			}
		}
		else{
			for(int i = 1; i <= n; i++){
				if(st[i][1]) printf("%lld ", to[i].size());
				else printf("1 ");				
			}
		} 
	}
	puts("");

	return 0;
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值