思路
-
会dsu on tree 的都不难看出这个是个dsu ,但是这道题需要查找结点深度小于等于指定值的结点个数,每次查找插入删除最多都为log。
-
显然用map插入删除可以做到log,但是查找不行,所以可以考虑用线段树,但是对每个权值都开一个线段树,内存肯定不够,所以这里就有了动态开点线段树。
-
动态开点线段树,原理实际上就是不像普通线段树那样一建树就把整棵树都建好,而是当第一次给某个这个区间插入值的时候,才给对应区间分配结点,这样左右子树的下标关系就不再是i << 1 和 i << 1 | 1 的关系了,所以需要用数组存储结点左右儿子位置。
动态开点线段树学习推荐
细节看代码
#include<bits/stdc++.h>
using namespace std;
#define IOS std::ios_base::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);// 快读
const int N = 1e5 + 10;
const int MAX = 2e7;
int h[N],to[N << 1],ne[N << 1];
int son[N],sz[N];
int cnt = 0,node = 0,fanode,k,n;
long long res = 0;
int len[N],v[N];
int lc[MAX],rc[MAX],sum[MAX];// lc结点的左儿子的下标 rc结点右儿子的下标, sum 结点的和
// 一定要保留下标0,不用 ,但vi范围为 0 ~ n,所以建根要从1 ~ n + 1
int query(int l,int r,int rt,int L,int R){// L R 为要查询区间, l,r为目前所在区间 rt 为sum的下标
if(rt == 0)//没有该区间
return 0;
if(L <= l && r <= R)
return sum[rt];
int m = (l + r) >> 1;
int val = 0;
if(L <= m)
val += query(l,m,lc[rt],L,R);
if(m + 1 <= R)
val += query(m + 1,r,rc[rt],L,R);
return val;
}
void update(int l,int r,int &rt,int id,int val){// rt为引用为的是儿子如果为空则更新
if(!rt) rt = ++node;// 为0 也就是为空,才建立结点
//cout << l << " " << r << " " << id << ' ' << val << " " << rt << '\n';
if(l == r){
sum[rt] += val;
return ;
}
int m = (l + r) >> 1;
if(m >= id)
update(l,m,lc[rt],id,val);
else update(m + 1,r,rc[rt],id,val);
sum[rt] = sum[lc[rt]] + sum[rc[rt]];
}
void add(int x,int y){
to[++cnt] = y;
ne[cnt] = h[x];
h[x] = cnt;
}
void dfs(int u,int dp){//暴力求重子树
len[u] = dp;
sz[u] = 1;
for(int i = h[u]; ~i; i = ne[i]){
int v = to[i];
dfs(v,dp + 1);
sz[u] += sz[v];
if(sz[v] > sz[son[u]]) son[u] = v;
}
}
void cal(int u){// 合并子树,求树的价值,根据val值可以选择消除信息
int x = 2*v[fanode] - v[u];
int y = k + 2*len[fanode] - len[u];
if(x >= 0 && x <= n && 1 <= y)// vi在合理范围才查询
res += query(1, n, x + 1, 1, y) * 2;
for(int i = h[u]; ~i; i = ne[i]){
int v = to[i];
cal(v);
}
}
void change(int u,int val){
int x = v[u] + 1;
if(~val)
update(1, n, x, len[u], 1);
else update(1,n, x, len[u], -1);
for(int i = h[u]; ~i; i = ne[i]){
int v = to[i];
change(v,val);
}
}
void get_ans(int u,int op){ //op为1表示为重子树 0为轻子树
for(int i = h[u];~i; i = ne[i]){
int v = to[i];
if(v == son[u]) continue;
get_ans(v,0);
}
if(son[u]) get_ans(son[u],1);//0 则无重子树
fanode = u;
for(int i = h[u];~i; i = ne[i]){
int v = to[i];
if(v == son[u]) continue;
cal(v);//合并重子树和轻子树
change(v,1);
}
int x = v[u] + 1;
// x = 4, len[u] = 2
update(1,n,x,len[u],1);
if(!op)
{
change(u,-1);
}
}
int main(){
IOS
memset(h,-1,sizeof h);
cin >> n >> k;
for(int i = 1; i <= n; ++i){
cin >> v[i];
}
for(int i = 1; i < n; ++i){
int x; cin >> x;
add(x,i + 1);
}
for(int i = 1 ; i <= n + 1; ++i)// vi 0 ~ n 所以建立 1 ~ n + 1分别为对应的根
{
int x = 0;
update(1, 1, x,0,0);// l r, root, id ,val
//cout << "----------" << '\n';
}
//cout << "----------" << '\n';
dfs(1,1); get_ans(1,1);
cout << res;
return 0;
}