【CCF-CSP】202312-3 树上搜索

题目背景

西西艾弗岛大数据中心为了收集用于模型训练的数据,推出了一项自愿数据贡献的系统。岛上的居民可以登录该系统,回答系统提出的问题,从而为大数据中心提供数据。为了保证数据的质量,系统会评估回答的正确性,如果回答正确,系统会给予一定的奖励。

近期,大数据中心需要收集一批关于名词分类的数据。系统中会预先设置若干个名词类别,这些名词类别存在一定的层次关系。例如,“动物”是“生物”的次级类别,“鱼类”是“动物”的次级类别,“鸟类”是“动物”的次级类别,“鱼类”和“鸟类”是“动物”下的邻居类别。这些名词类别可以被按树形组织起来,即除了根类别外,每个类别都有且仅有一个上级类别。 并且所有的名词都可以被归类到某个类别中,即每个名词都有且仅有一个类别与其对应。一个类别的后代类别的定义是:若该类别没有次级类别,则该类别没有后代类别;否则该类别的后代类别为该类别的所有次级类别,以及其所有次级类别的后代类别。

下图示意性地说明了标有星号的类别的次级类别和后代类别。

img

系统向用户提出问题的形式是:某名词是否属于某类别,而用户可以选择“是”或“否”来回答问题。该问题的含义是:某名词是否可以被归类到某类别或其后代类别中。

例如,要确定名词“鳕鱼”的类别,系统会向用户提出“鳕鱼是否属于动物”,当用户选择“是”时,系统会进一步询问“鳕鱼是否属于鱼类”,当用户选择“是”时,即可确定“鳕鱼”可以被归类到“鱼类”这一类别。

此外,如果没有更具体的分类,某一名词也可以被归类到非叶子结点的类别中。例如,要确定“猫”的类别,系统可以向用户提出“猫是否属于动物”,当用户选择“是”时,系统会进一步分别询问“猫”是否属于“鱼类”和“鸟类”,当两个问题收到了否定的答案后,系统会确定“猫”的类别是“动物”。

大数据中心根据此前的经验,已经知道了一个名词属于各个类别的可能性大小。为了用尽量少的问题确定某一名词的类别,大数据中心希望小 C 来设计一个方法,以减少系统向用户提出的问题的数量。

题目描述

小 C 观察了事先收集到的数据,并加以统计,得到了一个名词属于各个类别的可能性大小的信息。具体而言,每个类别都可以赋予一个被称为权重的值,值越大,说明一个名词属于该类别的可能性越大。由于每次向用户的询问可以获得两种回答,小 C 联想到了二分策略。他设计的策略如下:

  1. 对于每一个类别,统计它和其全部后代类别的权重之和,同时统计其余全部类别的权重之和,并求二者差值的绝对值,计为 wδ​;
  2. 选择 wδ​ 最小的类别,如果有多个,则选取编号最小的那一个,向用户询问名词是否属于该类别;
  3. 如果用户回答“是”,则仅保留该类别及其后代类别,否则仅保留其余类别;
  4. 重复步骤 1,直到只剩下一个类别,此时即可确定名词的类别。

小 C 请你帮忙编写一个程序,来测试这个策略的有效性。你的程序首先读取到所有的类别及其上级次级关系,以及每个类别的权重。你的程序需要测试对于被归类到给定类别的名词,按照上述策略提问,向用户提出的所有问题。

输入格式

从标准输入读入数据。

输入的第一行包含空格分隔的两个正整数 n 和 m,分别表示全部类别的数量和需要测试的类别的数量。所有的类别从 11 到 n 编号,其中编号为 1 的是根类别。

输入的第二行包含 n 个空格分隔的正整数 1,2,…,w1​,w2​,…,wn​,其中第 i 个数 wi​ 表示编号为 i 的类别的权重。

输入的第三行包含 n−1) 个空格分隔的正整数 p2​,p3​,…,pn​,其中第 i 个数 pi+1​ 表示编号为(i+1) 的类别的上级类别的编号,其中pi​∈[1,n]

接下来输入 m 行,每行一个正整数,表示需要测试的类别编号。

输出格式

输出 m 行,每行表示对一个被测试的类别的测试结果。表示按小 C 的询问策略,对属于给定的被测类别的名词,需要依次向用户提出的问题。

每行包含若干空格分隔的正整数,每个正整数表示一个问题中包含的类别的编号,按照提问的顺序输出。

思路

一看就是一个dfs+剪枝的题,n的范围最大是2000,做m个dfs也就1e6,时间够了。

因为每个点要算自己的后代与子树其他节点的w的差值,可以先用dfs求每个节点后代和,并求出当前子树的w总和。然后在dfs一次找差值绝对值最小的那个。如果查询点在查找到的点的后代中,下一次就搜这个子树,否则的话就把这个子树剪去。

代码

#include <bits/stdc++.h>
#define N 2010
using namespace std;
typedef long long ll;

int n,m,q,f[N];
vector<int> v[N];
ll w[N];

int vis[N],id;
ll sum,ret,num[N],minw,cnt;
void dfs(int x){//求当前w和 sum
	num[x]=w[x];
	sum+=w[x];
	for(int i=0;i<v[x].size();i++){
		int y=v[x][i];
		if(vis[y]) continue;
		dfs(y);
		num[x]+=num[y];
	}
}
void dfs1(int x){//找最小的w δ
	cnt++;
	if(abs(2*num[x]-sum)<minw) minw=abs(2*num[x]-sum),id=x;
	else if(abs(2*num[x]-sum)==minw&&x<id) id=x;
	for(int i=0;i<v[x].size();i++){
		int y=v[x][i];
		if(vis[y]) continue;
		dfs1(y);
	}
}
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>w[i];
    for(int i=2;i<=n;i++){
    	cin>>f[i];
    	v[f[i]].push_back(i);
	}
	for(int i=1;i<=m;i++){
		memset(vis,0,sizeof vis);
		cin>>q;
		set<int> s; //q的所有祖先,方便判断q是否在当前选择点的后代中
		while(q){
			s.insert(q);
			q=f[q];	
		}
		int rt=1;
		while(1){
			minw=1e18,id=0,cnt=0,sum=0;
			dfs(rt);//求当前w的和sum
			dfs1(rt);//找找最小的w δ
			if(cnt==1) break;//现在的子树只有一个点的话就不用问了
			cout<<id<<" ";//输出
			if(cnt==2) break;//子树有两个点的话确定一个不是,另一个就一定是了
			if(s.find(id)!=s.end()) rt=id;//如果q在id的后代里,那下次就搜id
			else vis[id]=1;//否则把id剪枝
		} 
		cout<<endl;
	}
    return 0;
}

  • 19
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值