题目描述
树上搜索
ccf csp第32次认证第三题
原题链接:http://118.190.20.162/view.page?gpid=T178
样例
输入
5 2
10 50 10 10 20
1 1 3 3
5
3
输出
2 5
2 5 3 4
思路
本题是一道大模拟题,用到的算法不难但杂,稍不注意就会出错
解题关键
1.对于每一个类别,统计它和其全部后代类别的权重之和,同时统计其余全部类别的权重之和,并求二者差值的绝对值,选择绝对值最小的类别。这个条件直接做需要时刻维护每次迭代二者差值的绝对值,比较麻烦,于是转化为求每个节点及后代权重和的两倍与当前树总权重差的绝对值,当两者绝对值最小时就可以得到当前查询的节点。
2.不仅要记录当前节点的孩子节点,也到同时记录当前节点的父节点。前者用链表存,后者直接用数组存。
3.数据会爆int ,要开long long
题解
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 2010;
int idx,h[N],e[N],ne[N],w[N];
int fa[N];
int n,m;
LL u[N],uu[N];
bool st[N];
//st是表示每个节点的状态,st=true说明这个节点在树中,可以访问,st=false说明这个节点已经被删除,不能访问
void add(int a,int b){
ne[idx]=h[a];
e[idx]=b;
h[a]=idx++;
}
LL dfs(int x){
LL sum=w[x];
for(int i=h[x];i!=-1;i=ne[i]){
int j=e[i];
uu[j]=dfs(j);
sum+=uu[j];
}
return sum;
}
//返回当前节点最有可能被查询的那个节点
int query(int head,LL all){
int temp=head;
for(int i=h[head];i!=-1;i=ne[i]){
int j=e[i];
if(!st[j]) continue;
int p=query(j,all); //找到它的子节点最有可能被查询的那个节点
if(abs(u[p]*2-all)<abs(u[temp]*2-all)) temp=p;//差值更小,直接更新即可
else if(abs(u[p]*2-all)==abs(u[temp]*2-all)&&p<temp) temp=p;//差值一样,取编号更小的节点
}
return temp;
}
int main(){
memset(h,-1,sizeof h);
cin>>n>>m;
for(int i=1;i<=n;i++) {
cin>>w[i];
}
fa[1]=0;
for(int i=2;i<=n;i++){
int a;
cin>>a;
fa[i]=a;
add(a,i);
}
uu[1]=dfs(1);
while(m--){
memset(st,true,sizeof st);
memcpy(u,uu,sizeof uu);//每次迭代前利用uu重置u数组,典型的空间换时间
int head=1;
int x;
cin>>x;//x是我们想要的那个节点
while (1)
{
int t=query(head,u[head]);//t是现在要判断的节点
bool fl=false;
for(int i=h[head];i!=-1;i=ne[i]){
int j=e[i];
if(!st[j]) continue;
fl=true;
}
if(!fl) break;//当现在整个树只有一个节点时,退出循环
cout<<t<<" ";
bool flag=false;
for(int i=x;i!=0;i=fa[i]){
if(i==t) flag=true; //目标节点是否为当前节点的后代节点
}
if(flag){
head=t;//如果是则头节点改成当前节点
}
else{
st[t]=false;//如果不是那么一定在另一部分,头节点不变,但要把当前节点及其后代节
//点从树中删除,只需要修改当前节点的状态即可,因为dfs查询的时候是从上往下查询
//到,到不了父节点,自然也到不了它的子节点。
for(int i=fa[t];i!=0;i=fa[i]){
u[i]-=u[t];//这个子树删除了,它的父节点的加权和也要修改
}
}
}
puts("");
}
return 0;
}