202312-3 树上搜索

文章讲述了在CCF-CSP认证考试中的一个问题,涉及使用深度优先搜索(DFS)求解两个数组(w1和w2),并通过vis数组管理结点删除状态。两种解法分别涉及判断类别归属并根据需求选择保留类别或其余类别。
摘要由CSDN通过智能技术生成

原题链接:

计算机软件能力认证考试系统

题解:

解法1:

分析上述流程:

  1. 对于“其余全部类别的权重之和”,这个我们可以通过一个dfs求得,记为w1数组
  2. 接着对于“求两者差值的绝对值”,这个我们可以通过abs(sum - 2 * w1[i]),其中sum代表当前树的权重之和(不含已删除结点)。
  3. 因为保留结点涉及到删除其余结点的操作,在这里我们用一个数组vis记录某个结点是否被删除。
  4. 如何判断当前类别属于该类别呢?也可以通过一个dfs实现,遍历当前类别的所有后代,若遇见相同的即为其后代类别。
  5. 如果回答“是”,那么需要“仅保留该类别及其后代类别”:首先我们需要删除其余所有类别结点(通过dfs实现,遇见其余类别将vis置为1,遇见当前类别返回即可),同时需要将当前类别的结点作为删除后子树的根节点,继续进行下一轮迭代。
  6. 如果回答“否”,那么需要“仅保留其余类别”:这个只需要通过dfs将当前类别及后代类别全部置为1即可,根结点无须发生变化。
  7. 重复上述步骤,直到vis数组中0的个数为1停止。
  8. 记得每次query之前都要初始化状态。
解法2:

还有一种很巧妙的办法,只需要一个dfs即可,具体参见大佬Pujx的博客:

  1. 大致思路是维护一个数组vis,每次迭代过程中当求解w1数组的时候对于遍历到的每个结点vis都加1。
  2. 对于“判断当前类别是否属于该类别”,通过fa不断递归上去进行判断即可;
  3. 如果回答“是”,那么需要“仅保留该类别及其后代类别”:那么仅需要将当前结点作为根结点即可,这样在求w2数组的时候直接判断当前结点的vis是否等于根结点的vis即可,相同则代表该结点未被删除。
  4. 如果回答“否”,那么需要“仅保留其余类别”:直接将当前类别的vis赋值为-1即可,这样子在求w1数组的时候遇见-1直接返回即可,仅仅计算其余类别的w1。
  5. 直到最后当前子树仅剩下一个结点停止。

CCF-CSP认证考试 202312-3 树上搜索 100分题解_第32次csp-CSDN博客

代码:

#include<bits/stdc++.h>
using namespace std;
const int N = 2e3 + 10;
#define int long long
vector<int> v[N], v1[N];//表示父子关系
int val[N];//存储结点的权重
int w1[N];//存储结点及子结点的权重和
int w2[N];//存储两大类别的绝对值差值
int n, m;
int vis[N];//代表某个点是否已被删除
bool is_child;
int root = 1;

int dfs1(int cur) {//求w1数组
	if (!vis[cur])
		w1[cur] = val[cur];
	for (auto i : v[cur]) {
		if (!vis[i])
			w1[cur] += dfs1(i);
	}
	return w1[cur];
}

void dfs2(int cur, int obj) {//仅保留该结点和子结点
	if (cur != obj)
		vis[cur] = 1;
	for (auto i : v[cur]) {
		if (i != obj)
			dfs2(i, obj);
	}
}

void dfs3(int cur) {//杀死该结点和子结点
	vis[cur] = 1;
	for (auto i : v[cur])
		dfs3(i);
}

void dfs4(int cur, int obj) {//判断obj是否属于cur的孩子
	if (cur == obj) is_child = 1;
	for (auto i : v[cur])
		dfs4(i, obj);
}

int get_num() {//求存活结点个数
	int res = 0;
	for (int i = 1;i <= n;i++) res += vis[i];
	return n - res;
}

void find(int q) {
	while (get_num() > 1) {//当存活结点个数大于1时
		int sum = dfs1(root);
		int min = sum, idx;
		for (int i = 1;i <= n;i++) {
			if (!vis[i]) {
				w2[i] = abs(sum - 2 * w1[i]);
				if (w2[i] < min) {
					min = w2[i];
					idx = i;
				}
			}
			else w2[i] = 0;
		}
		cout << idx << " ";
		dfs4(idx, q);//判断q是否是idx的孩子
		if (is_child) {//仅保留该结点和子结点
			dfs2(1, idx);
			root = idx;
			is_child = 0;
		}
		else//杀死该结点和子结点
			dfs3(idx);
	}
	cout << endl;
}

signed main() {
	cin >> n >> m;
	for (int i = 1;i <= n;i++) cin >> val[i];
	for (int i = 2;i <= n;i++) {
		int t;cin >> t;
		v[t].push_back(i);
	}
	while (m--) {
		int q;cin >> q;
		memset(vis, 0, sizeof(vis));//初始化所有结点
		root = 1;
		find(q);
	}
}

坑点:

坑点1:

注意“向用户询问名词是否属于该类别”这个操作,并不是简单的判断idx和query是否相等,而是看当前的idx是否在以query为根节点所在的子树中。

坑点2:

由于最多有2000个结点,每个结点的权重最大为10^7,因此总权重最大为2*10^10,因此需要开LL。

  • 20
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值