毛毛虫 联想24年春招-后端开发工程师
树是一张 n 个点 n-1 条边的无向联通图,每两个点都有唯一的一条简单路径。有根树是指以其中一个点为根节点的树,叶子节点是指除根节点外度数为 1 的节点。一个点的度数是指与其相连的点的个数。有根树上,一个点的深度是指其与根节点之间的简单路径的边数。
在某一棵以 1 为根的有根树上,有两个节点 a,b 上各存在一只毛毛虫。这两只毛毛虫只会往深度更大的点前进,当毛毛虫走到叶子节点时会停下。设第一只毛毛虫可能走到的节点为p1,第二只毛毛虫可能走到的节点为p2,你想要知道二元组(p1,p2)的个数(p1可以等于p2)。一共有 Q 次询问。
时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 256M,其他语言512M
输入描述:
第一行两个正整数n,Q(1 <= n,Q <= 50000)。
第二行 n-1 个正整数f2,f3…fn,(1 <= fi < i),表示树上节点 i 与 fi 之间有一条边。
第三行 Q个正整数a1,a2…aQ(1<= ai <=n) ;
第四行 Q个正整数b1,b2…bQ(1<=bi<=n, ai != bi)。
第三行和第四行表示 ai,bi 是第i 个查询对应的两只毛毛虫所在的节点。
输出描述:
为了避免输出量较大,你需要输出所有询问的答案的异或和。
此题描述较为复杂,但属于经典的树形dp的题目,难度中等偏上。主要注意一下几点:
- 构建树:题目描述的构建树的方式其实不是很严谨,但是推测是边,指的就是父亲节点。
- 此题是多叉树,而不是普通的二叉树。
- 由于多次查询,由递归记忆化的思想,可以使用数组存储每个节点的叶子孩子数量。
#include <iostream>
#include <vector>
using namespace std;
struct Node{
vector<Node*> sons;
int val=0;
Node(int value){
val=value;
}
};
int getLeafChildNum(vector<Node*>& arr, int p,vector<int> & leafChildNum){
if(leafChildNum[p]!=-1){
return leafChildNum[p];
}
if(arr[p]->sons.size()==0){
leafChildNum[p]=1;
return 1;
}
leafChildNum[p]=0;
for(int i=0;i<arr[p]->sons.size();++i){
//向子节点要答案:dp的思想,解决小问题,再利用其答案。
leafChildNum[p]+=getLeafChildNum(arr, arr[p]->sons[i]->val, leafChildNum);
}
return leafChildNum[p];
}
int zhongshu(vector<Node*>& arr, int p1,int p2,vector<int> & leafChildNum){
return getLeafChildNum(arr,p1,leafChildNum) * getLeafChildNum(arr,p2,leafChildNum);
}
int main() {
int n, Q;
cin>>n>>Q;
vector<Node *> arr(n+1);
vector<int> leafChildNum(n+1);
for (int i=0; i<=n; ++i) {
arr[i]=nullptr;
leafChildNum[i]=-1;
}
Node* root = new Node(1); //边可能是根,可能是儿子,是否按照顺序
arr[1]=root;
for(int i=2;i<=n;++i){
Node* cur=new Node(i);
int parval;
cin>>parval;
Node* par=arr[parval];
par->sons.push_back(cur);
arr[i]=cur;
}
vector<int> p1(Q),p2(Q);
for(int i=0;i<Q;++i){
cin>>p1[i];
}
for(int i=0;i<Q;++i){
cin>>p2[i];
}
int cnt=0;
for(int i=0;i<Q;++i){
int res = zhongshu(arr,p1[i],p2[i],leafChildNum);
// cout<<res<<endl;
cnt^=res;
}
cout<<cnt;
}