树上搜索
第一次做,感觉特别复杂,要考虑很多情况。特别是如何删除树枝、如何保留新的树,都是难点。最后看了CCF-CSP认证考试 202312-3 树上搜索(不用struct建树、超短代码!这篇,将自己的理解写在了下面。
本来我一开始想使用的是BFS遍历树,但是书写起来显然没有DFS轻便。我们对树的表示都是差不多的,都是使用了一个数组来记录每一个节点的子节点,方便从上往下遍历。
但是我缺乏的地方,或者说在本题最大是收获就是对book的妙用、set判断节点位置。
首先谈谈book的妙用吧。本来我以为book只能用来标记两种状态,be or not be(doge)。但是这题让我大开眼界。可以使用book[ cur ]++区分不同状态下的树,这极大方便了我们最后计算和比较delta的时候。试想,如果不能用book区分树,我们会老老实实对树进行修剪,遍历的时候也会老老实实从树根开始,这样太麻烦了。
其次是判断节点位置。这里使用了set将goal的所有父结点都加上去了。我一开始还没反应过来,但是写到后面才知道,这样会极大方便我们确定获得的节点相对goal节点的位置。只要判断set中是否存在这个节点即可!如果存在,那就是其父节点!这让我想到并查集,但是并查集是一个一个连着的,需要一层一层往上调。
AC:
//树上搜索 ans
#include <iostream>
#include <vector>
#include <set>
#include <cmath>
using namespace std;
#define ll long long
const int N=2e3+2;
int n,m;
int goal;
vector<int>w(N,0); //单个类别权重
vector<ll>mulw(N,0); //记录当前和后代的总权重
vector<int>fa(N,0); //上级
vector<vector<int>>son(N); //子类
vector<int>book(N,0); //记录哪些节点被删除了
ll dfs(int cur)
{
mulw[cur]=w[cur];
book[cur]++; //我觉得这里才是点睛之笔!此处之所以是++,而不是=1,有大学问的
//book的标记有两个作用,第一就是下面的删除类,第二就是main函数中用来区分不同的树!
//比如,我们每次遍历新的树,此时根=2,book值为3,那么其子树的book值也为3。
//当我们进行删除操作后,新树根=3,book值为4,子树的book值也为4。
//那么那些book值为3的节点,就不在这棵树上了!
for(int i=0;i<son[cur].size();i++)
{
int nex=son[cur][i];
if(book[nex]==-1) continue; //被标记过的,跳过
mulw[cur]+=dfs(nex); //否则当前总权重为后代权重相加
}
return mulw[cur];
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>w[i];
}
for(int i=2;i<=n;i++)
{
cin>>fa[i];
son[fa[i]].push_back(i);
}
while(m--)
{
cin>>goal; //待检测目标
//每次检测都要更新一下
book.assign(n+1,0);
int curRoot=1; //当前root
int cutNum=son[goal].size(); //goal的子节点个数
set<int>belongTo;
for(int cur=goal;cur!=0;cur=fa[cur]) //将goal的上级全部装入set中
belongTo.insert(cur); //set之后用来判断goal是否为询问类型的后代
while(cutNum!=0 || curRoot!=goal)
{//最后是只剩一个,退出循环。所以要满足curRoot指向goal,且goal变成叶子节点,没有后代
dfs(curRoot); //dfs的做用是计算当前树的所有类的权值
//获取到权值之后就相减,但是怎么减呢?有两点需要把握:
//1.根类的权值就是总权值。我们只需要将根类的权值和子类的权值进行计算即可
//2.我们用book的值来区分最新的树的节点,仔细看看下面的操作:
vector<ll>delta(n+1); //差值存放
for(int i=1;i<=n;i++) //这里还没有区分树
delta[i]=abs(mulw[i]-(mulw[curRoot]-mulw[i])); //此类权重-剩余权重
int askItem=curRoot; //将要询问的类,先默认设置为curRoot
for(int i=1;i<=n;i++)
if(book[curRoot]==book[i] && delta[i]<delta[askItem]) //book[curRoot]==book[i]将不是当前树的节点排除
askItem=i;
cout<<askItem<<" ";
//askItem有2种情况:1.为goal的父,2.为goal或者其子
if(belongTo.count(askItem)) //如果askItem是goal的父节点
curRoot=askItem; //更新根
else book[askItem]=-1; //否则标记,遍历树的时候将其删除
if(fa[askItem]==goal) //如果是其子,那删除的时候,就要记录goal剩下的子节点个数
cutNum--;
}
cout<<endl;
}
return 0;
}