原题链接:
题解:
解法1:
分析上述流程:
- 对于“其余全部类别的权重之和”,这个我们可以通过一个dfs求得,记为w1数组
- 接着对于“求两者差值的绝对值”,这个我们可以通过abs(sum - 2 * w1[i]),其中sum代表当前树的权重之和(不含已删除结点)。
- 因为保留结点涉及到删除其余结点的操作,在这里我们用一个数组vis记录某个结点是否被删除。
- 如何判断当前类别属于该类别呢?也可以通过一个dfs实现,遍历当前类别的所有后代,若遇见相同的即为其后代类别。
- 如果回答“是”,那么需要“仅保留该类别及其后代类别”:首先我们需要删除其余所有类别结点(通过dfs实现,遇见其余类别将vis置为1,遇见当前类别返回即可),同时需要将当前类别的结点作为删除后子树的根节点,继续进行下一轮迭代。
- 如果回答“否”,那么需要“仅保留其余类别”:这个只需要通过dfs将当前类别及后代类别全部置为1即可,根结点无须发生变化。
- 重复上述步骤,直到vis数组中0的个数为1停止。
- 记得每次query之前都要初始化状态。
解法2:
还有一种很巧妙的办法,只需要一个dfs即可,具体参见大佬Pujx的博客:
- 大致思路是维护一个数组vis,每次迭代过程中当求解w1数组的时候对于遍历到的每个结点vis都加1。
- 对于“判断当前类别是否属于该类别”,通过fa不断递归上去进行判断即可;
- 如果回答“是”,那么需要“仅保留该类别及其后代类别”:那么仅需要将当前结点作为根结点即可,这样在求w2数组的时候直接判断当前结点的vis是否等于根结点的vis即可,相同则代表该结点未被删除。
- 如果回答“否”,那么需要“仅保留其余类别”:直接将当前类别的vis赋值为-1即可,这样子在求w1数组的时候遇见-1直接返回即可,仅仅计算其余类别的w1。
- 直到最后当前子树仅剩下一个结点停止。
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。