Codeforces Round 656 (Div. 3) F. Removing Leaves

Problem Description

给出一棵 n n n 个节点的无根树。你可以进行以下操作:

选择 k k k 个共同父节点的叶子节点,将 k k k 个节点和与父节点相连的边删去。

求最大操作次数。

Input

第一行输入一个整数 t t t ( 1 ≤ t ≤ 2 × 1 0 4 ) (1 \le t \le 2 \times 10^4) (1t2×104) ,表示测试组数。

接下来每组测试数据第一行输入两个整数 n n n , k k k ( 2 ≤ n ≤ 2 × 1 0 5 , 1 ≤ k < n ) (2 \le n \le 2 \times 10^5,1 \le k <n) (2n2×105,1k<n) ,表示节点个数和删除的节点限制个数。接下来 n − 1 n-1 n1 行每行输入两个整数 x i , y i x_i,y_i xi,yi ( 1 ≤ x i , y i ≤ n ) (1 \le x_i,y_i \le n) (1xi,yin) ,表示 x i x_i xi y i y_i yi 之间有一条边。

题目保证 ∑ n ≤ 2 × 1 0 5 \sum n \le 2 \times 10^5 n2×105

Output

输出最大操作次数。

Solution

首先考虑到删 k k k 个叶子节点的操作是从原树的叶子节点向内进行的,这是具有拓扑关系的。

那么我们可以去考虑每个节点,将其视作根节点即可以找出所以情况的答案,但是这样复杂度是 O ( n 2 ) O(n^2) O(n2) ,此时我们便考虑通过换根 d p dp dp 去转移根节点状态优化。

d p u , 0 dp_{u,0} dpu,0 表示为 u u u 是否为可删节点,如何为 0 0 0 即为可删节点,如果不为 0 0 0 则为不可删节点。

d p u , 1 dp_{u,1} dpu,1 表示为 u u u 的儿子节点中可删节点的个数。

a n s u ans_u ansu 表示 u u u 为父节点的子树对答案的贡献。

第一遍 d f s dfs dfs 时,我们任意找一个节点为根,得到各个点的状态,状态转移为:

{ d p u , 1 = ∑ [ d p v , 0 = = 0 ] , a n s u = ⌊ d p u , 1 / k ⌋ + ∑ a n s v , d p u , 0 = [ d p u , 1 % k ≠ 0 ] + ∑ d p v , 0 , \left\{\begin{array}{l} dp_{u,1}=\sum[dp_{v,0} == 0], \\ans_u=\left \lfloor dp_{u,1} / k\right \rfloor + \sum ans_v, \\dp_{u,0}=[dp_{u,1}\%k \not= 0]+\sum dp_{v,0}, \end{array} \right. dpu,1=[dpv,0==0],ansu=dpu,1/k+ansv,dpu,0=[dpu,1%k=0]+dpv,0,

然后第二遍 d f s dfs dfs 时,首先要将 u u u 状态更新为儿子节点, a n s u − = a n s v ans_u-=ans_v ansu=ansv d p u , 0 − = d p v , 0 dp_{u,0}-=dp_{v,0} dpu,0=dpv,0

然后考虑转移的节点 v v v 是否为可删节点 ,如果是,考虑以下两种情况:

  1. d p u , 1 % k = = 0 dp_{u,1}\% k == 0 dpu,1%k==0 ,说明 v v v 是对 u u u 的答案有贡献的,且 u u u 会变成不可删节点,我们需要令 a n s u − − ans_u-- ansu , d p u , 0 + + dp_{u,0}++ dpu,0++
  2. ( d p u , 0 − 1 ) % k = = 0 (dp_{u,0}-1)\%k==0 (dpu,01)%k==0 ,说明在去掉这个节点后, u u u 的可删节点将没有残余,可能成为不可删节点,所以要 d p u , 0 − − dp_{u,0}-- dpu,0

然后我们更新 v v v 的状态,首先判断一下是否 d p u , 0 = = 0 dp_{u,0}==0 dpu,0==0 ,如果是则 u u u 变成可删节点, d p v , 1 + + dp_{v,1}++ dpv,1++ 。之后类似第一遍的 d f s dfs dfs 转移即可,不过要注意需要先减去旧状态对状态的影响,下面中 b e 1 be_1 be1 表示 d p v , 1 dp_{v,1} dpv,1 为儿子节点时的状态:

{ d p v , 0 = d p v , 0 + d p u , 0 + [ d p v , 1 % k ≠ 0 ] − [ b e 1 % k ≠ 0 ] , a n s v = a n s v + a n s u + ⌊ d p v , 1 / k ⌋ − ⌊ b e 1 / k ⌋ , \left\{ \begin{array}{l} dp_{v,0} = dp_{v,0}+dp_{u,0} + [dp_{v,1}\%k \not= 0]-[be_{1}\%k \not= 0], \\ans_v=ans_v+ans_u+\left \lfloor dp_{v,1}/k \right \rfloor-\left \lfloor be_1/k \right \rfloor,\end{array}\right. {dpv,0=dpv,0+dpu,0+[dpv,1%k=0][be1%k=0],ansv=ansv+ansu+dpv,1/kbe1/k,

然后遍历下一个节点统计最大值,之后记得回溯状态即可。

Code

#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
constexpr int N = 2e5 + 10, M = 4e5 + 10;
int n, k;
int head[N], e[M], ne[M], idx;
inline void addedge(int a, int b) {
	e[idx] = b, ne[idx] = head[a], head[a] = idx++;
}
int res, ans[N];
array<int, 2>dp[N];
void initial_dfs(int u, int fa) {
	for (int i = head[u]; i != -1; i = ne[i]) {
		int v = e[i];
		if (v == fa)continue;
		initial_dfs(v, u);
		ans[u] += ans[v], dp[u][0] += dp[v][0];
		if (!dp[v][0])dp[u][1]++;//儿子节点为可删节点才对答案有贡献
	}
	if (dp[u][1] % k)dp[u][0]++;//如果该节点操作后有不可删节点时
	ans[u] += dp[u][1] / k;
}
void dfs(int u, int fa) {
	res = max(res, ans[u]);
	for (int i = head[u]; i != -1; i = ne[i]) {
		int v = e[i];
		if (v == fa)continue;
		auto a = ans[u], b = ans[v];
		auto l = dp[u], r = dp[v];
		ans[u] -= ans[v], dp[u][0] -= dp[v][0];
		if (!dp[v][0]) {//当转移节点是可删节点时
			if (dp[u][1] % k == 0) ans[u]--, dp[u][0]++;//如果v对u的答案是有贡献的
			if (--dp[u][1] % k == 0)dp[u][0]--;//如果v可能是限制u成为可删节点
		}
		if (!dp[u][0])dp[v][1]++;//如果u变成可删节点
		//将v的状态转移成根节点状态
		dp[v][0] += (dp[u][0] + (dp[v][1] % k != 0) - (r[1] % k != 0));
		ans[v] += (ans[u] + dp[v][1] / k - r[1] / k);
		dfs(v, u);
		ans[u] = a, ans[v] = b, dp[u] = l, dp[v] = r;//状态回溯
	}
}
void solve() {
	cin >> n >> k;
	res = idx = 0;
	for (int i = 0; i <= n; i++) {
		ans[i] = dp[i][0] = dp[i][1] = 0;
		head[i] = -1;
	}
	for (int i = 1; i <= n - 1; i++) {
		int a, b;
		cin >> a >> b;
		addedge(a, b), addedge(b, a);
	}
	initial_dfs(1, 0);
	dfs(1, 0);
	cout << res << endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t;
	cin >> t;
	while (t--)solve();
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值