NOIP模拟 拆网线 - 贪心策略+dp

题目大意:

给一颗n个节点的树,保留最少的边,使得每个连通块的大小都大于等于2,并且连通块的点数和等于k.

题目分析:

要想留下的边数最少,就要尽量多的选择单独的边,这里就要贪心:尽可能多的选择单独的边。 如果选出的边*2大于等于了k,就直接根据k的奇偶返回答案。如果不够,就将剩下的点挂在每一条单链上。
明确思路,只要求出最多的单独的边问题就迎刃而解:dp[i][0]表示i节点不向儿子连边,其子树中最多的单独的边,dp[i][1]表示i节点向某一个儿子连边其子树中最多的单独的边。

\[dp[i][0] = \sum{dp[son[i]][1]} \]

\[dp[i][1] = (\sum{dp[son[i]][1]}) + max\{-dp[son[i]][1] + dp[son[i]][0]\} + 1 \]

code

#include<bits/stdc++.h>
using namespace std;
#define maxn 100050
namespace IO{
	inline int read(){
		int i = 0, f = 1; char ch = getchar();
		for(; (ch < '0' || ch > '9') && ch != '-'; ch = getchar());
		if(ch == '-') f = -1, ch = getchar();
		for(; ch >= '0' && ch <= '9'; ch = getchar()) i = (i << 3) + (i << 1) + (ch - '0');
		return i * f;
	}
	inline void wr(int x){
		if(x < 0) x = -x, putchar('-');
		if(x > 9) wr(x / 10);
		putchar(x % 10 + '0');
	} 
}using namespace IO;
int T, n, k, dp[maxn][2];
vector<int> adj[maxn];

inline void DP(int x, int f){
//	cout<<x<<"!!!";
	int maxx = 0;
	for(int i = adj[x].size() - 1; i >= 0; i--){
		int v = adj[x][i];
		if(v == f) continue;
		DP(v, x);
		dp[x][0] += dp[v][1];
		maxx += dp[v][1];
	}
	for(int i = adj[x].size() - 1; i >= 0; i--){
		int v = adj[x][i];
		if(v == f) continue;
		dp[x][1] = max(dp[x][1], maxx - dp[v][1] + dp[v][0] + 1); 
	}
//	cout<<x<<" "<<dp[x][1]<<" "<<dp[x][0]<<endl;
}

int main(){
	freopen("h.in",  "r", stdin);
	scanf("%d", &T);
	while(T--){
		for(int i = 1; i <= n; i++) adj[i].clear();
		memset(dp, 0, sizeof dp);
		n = read(), k = read();
		for(int i = 1; i < n; i++){
			int x = read();
			adj[x].push_back(i + 1), adj[i + 1].push_back(x);
		}
		DP(1, 0);
//		continue;
		if(max(dp[1][0], dp[1][1]) * 2 >= k){
			if(k & 1){
				wr((k - 3) / 2 + 2), putchar('\n');
			}
			else{
				wr(k / 2), putchar('\n');
			}
		}
		else wr(max(dp[1][0], dp[1][1]) + k - 2 * (max(dp[1][0], dp[1][1]))), putchar('\n');
	}
	return 0;
}
posted @ 2017-10-23 14:52  CzYoL  阅读(264)  评论(0编辑  收藏  举报