【CF】1695D1-Tree Queries(Easy Version) 题解

传送门:1695D1
标签:动态规划

题目大意

给定一棵无根树,其中包含n个顶点。在树中隐藏了一个未知的顶点x,你需要通过查询来找出这个顶点。你可以进行k次查询,每次查询选择一个顶点v_i,在完成所有查询后,你会得到k个数字d_1, d_2, …, d_k,其中d_i表示从v_i到顶点x的最短路径上的边的数量。请注意,你知道每个距离对应哪个查询。请确定最小的k值,使得存在一些查询v_1, v_2, …, v_k,这些查询总能让你唯一地识别出顶点x,你不需要实际输出这些查询。

输入:文件的第一行包含一个整数t(1≤t≤100),表示测试用例的数量。接下来是对测试用例的描述。每个测试用例的第一行包含一个整数n(1≤n≤2000),表示树中的顶点数量。接下来的n-1行每行包含两个整数x和y(1≤x,y≤n),表示在树中有一条连接顶点x和y的边。
保证给出的边形成了一个树。保证所有测试用例的n之和不超过2000。

输出格式:对于每个测试用例,输出一个非负整数,表示所需的最少查询次数。

算法分析

  • 如果n=1,则无需任何查询,因为只有一个顶点。否则,我们需要至少一次查询。如果我们固定一个节点u,并强制它成为查询,我们可以将树根固定在u上,并执行贪婪DFS以计算答案。需要注意的是,因为我们保证了根是一个查询,所以当我们为任何节点v计算答案时,我们可以假设要么是v要么是不在v子树中的某个顶点已经被查询过了。
  • 我们定义ans[v]为区分v子树内所有顶点所需的最小查询次数,前提是v或不在v子树内的某个顶点已经进行了查询。注意到对于v的所有孩子c,我们需要能够区分c子树内的所有顶点,因此我们有ans[v]≥∑c ans[c]。此外,最多只能有一个孩子c的子树没有查询,否则这些孩子的所有子树都将无法通过查询区分开。如果有x>1这样的孩子,我们可以查询前x-1个孩子,这样就足以区分这些x个子树中的所有顶点。使用这个x的定义,我们的最终公式是:ans[v]=∑c ans[c]+max(0,x−1)
  • 对于每个可能的根,我们递归地执行DFS来计算这些答案。答案是最小的ans[root]+1,其中+1是因为我们要对根进行查询。复杂度:O(n^2)

代码实现

#include <bits/stdc++.h>
using namespace std;
const int N = 2010;
int t, n;
int d[N];
vector<int> e[N];

int dfs(int u, int fa){
	int cnt = 0, sum = 0;
	for (auto v : e[u]){
		if (v == fa) continue;
		int x = dfs(v, u);
		sum += x;
		if (!x) cnt ++ ;
	}
	return sum + max(0, cnt - 1);
}

void solve(){
	cin >> n;
	for (int i = 1; i <= n; i ++ ){
		e[i].clear();
		d[i] = 0;
	}
	for (int i = 1; i < n; i ++ ){
		int u, v;
		cin >> u >> v;
		e[u].push_back(v);
		e[v].push_back(u);
		d[u] ++ , d[v] ++ ;
	}
	if (n == 1){
		cout << 0 << '\n';
		return;
	}
	int ans = 0;
	for (int i = 1; i <= n; i ++ ){
		ans = max(ans, max(1, dfs(i, 0)));
	}
	cout << ans << '\n';
}

int main(){
	cin >> t;
	while (t -- ) solve();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值