首页 > 其他分享 >Codeforces Round 656 (Div. 3) F. Removing Leaves

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

时间:2024-03-08 22:12:04浏览次数:18  
标签:删节 int Removing Leaves Codeforces dfs ans 节点 dp

Problem Description

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

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

求最大操作次数。

Input

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

接下来每组测试数据第一行输入两个整数 \(n\) , \(k\) \((2 \le n \le 2 \times 10^5,1 \le k <n)\) ,表示节点个数和删除的节点限制个数。接下来 \(n-1\) 行每行输入两个整数 \(x_i,y_i\) \((1 \le x_i,y_i \le n)\) ,表示 \(x_i\) 和 \(y_i\) 之间有一条边。

题目保证 \(\sum n \le 2 \times 10^5\) 。

Output

输出最大操作次数。

Solution

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

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

\(dp_{u,0}\) 表示为 \(u\) 是否为可删节点,如何为 \(0\) 即为可删节点,如果不为 \(0\) 则为不可删节点。

\(dp_{u,1}\) 表示为 \(u\) 的儿子节点中可删节点的个数。

\(ans_u\) 表示 \(u\) 为父节点的子树对答案的贡献。

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

\[\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.\]

然后第二遍 \(dfs\) 时,首先要将 \(u\) 状态更新为儿子节点, \(ans_u-=ans_v\) , \(dp_{u,0}-=dp_{v,0}\) 。

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

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

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

\[\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.\]

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

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();
}

标签:删节,int,Removing,Leaves,Codeforces,dfs,ans,节点,dp
From: https://www.cnblogs.com/w1ck/p/18061958

相关文章

  • Codeforces Round 932 (Div. 2)
    目录写在前面ABCDE写在最后写在前面比赛地址:https://codeforces.com/contest/1935。被精心构造的C的样例鲨了的一集。妈的天使骚骚☆REBOOT完全就是他妈拔作啊我草,要是被人知道我他妈推了全线要被笑话一辈子吧、、、A签到。操作偶数次,则答案仅可能为s或reverse(s)+s......
  • Codeforces 专区
    CodeforcesRound#932(Div.2)怎么只会A啊。\(\text{ProblemA}\)题目大意对于一个字符串\(s\),可以进行\(n\)次操作(\(n\)为偶数),每次操作有两种形式:令\(t\)为\(s\)的反串,\(s=s+t\)。令\(t\)为\(s\)的反串,\(s=t\)。要求操作完后\(s\)的字典序最小,并输......
  • Codeforces Round 931div2补题
    B.YetAnotherCoinProblem真[https://www.bilibili.com/video/BV1o2421M7pV/]不同硬币之间有倍数关系,使得一定数量后的小硬币可以被大硬币替代达到最优方案,而每个小硬币在最优下有可能的数量如下,进行枚举后找到最优方案。1:不多于2个(3个1会被3替代)3:不多于一个(2个3......
  • 1935.Codeforces Round 932 (Div. 2) - sol
    20240306逊哎~打一半去写申请书然后12点睡觉,相对成功!第二天早上起来把赛时发愣的C和F切了。CodeforcesRound932(Div.2)A.EntertainmentinMACCongratulations,youhavebeenacceptedtotheMaster'sAssistanceCenter!However,youwereextremelybore......
  • Codeforces Round 932 (Div. 2)
    CodeforcesRound932(Div.2)A-EntertainmentinMAC解题思路:如果翻转字符小于原字符,那么一直翻转即可。否则,翻转\(n-1\)次,然后添加一次。代码:#include<bits/stdc++.h>usingnamespacestd;usingll=longlong;usingpii=pair<ll,ll>;typedefdoubledb;......
  • Codeforces Round 930 (Div. 1)
    Preface虽然难得有Div1的CF,但由于在周四晚上学校要断电就没打这两天有空去补了下发现好像错过了最适合我的一场,AB都是一眼题,C是经典图论建模,D是DS典题没有Counting我直接爽飞,不过估计现场打的话写个ABC差不多了,D的码量还是挺大的A.BitwiseOperationWizard很有意思的一个......
  • CodeForces 1540D Inverse Inversions
    洛谷传送门CF传送门小清新题。首先容易发现每个合法的\(b\)唯一对应一个排列,大概就是每个时刻排列元素的相对顺序,然后插入到相应的位置。但是这样太麻烦了。发现题目只要求求单点的\(p\)值。这应该有更简单的方法。考虑令\(b_i\getsi-b_i\)表示\(p_i\)在前缀\(......
  • Codeforces edu 156 C题
    https://codeforces.com/contest/1886/problem/C思路这道题的核心问题是:给你一个字符串s,你要删除k个字母,你要找出删除k个字母后字典序最小的s。为了使字典序最小,我们就应该把字符串删成递增的样子stringtmp="";//tmp用来存删完后的字符串s+='$';//s的末尾加一个比'......
  • Educational Codeforces Round 162 E 点分治 虚树 树形dp
    传送门给出\(n\le2\cdot10^5\)的一棵树,每个节点有一个颜色。求出路径长度为\(2\)路径首端和尾端拥有相同颜色,且路径上其他点不存在相同颜色的点的路径条数。当时看错题了,把颜色抽出来后没法做了。后来感觉能点分治,然后把题看对了,遂写了一个极其抽象的点分治。除此之外,把某......
  • Codeforces Round 930 (Div. 1) C dij 建图
    离较好的方法差一点。考虑到了可以按照枚举属性并按照当前属性从小到大排序,这样可以从一个点到大另一个点。设当前在排序序列中点为\(i\)当\(i\)走向\(k,i>=k\)需要支付\(c_k\)的代价。而\(i\)到\(k,i<k\)则需\(k-i+c_k\)的代价。则对于不同的\(i\)由于代价没有连续性,当时想......