题目描述
假定我们有一棵有根树,其中每个点上有权。它被称为树堆当且仅当每个点的权值都大于等于它的所有孩子。
现在我们有一棵有根树,它的每个点上有权。我们可以不断对它进行如下的操作:选择一个非根结点 v v v,删除 v v v,然后将 v v v的所有孩子连到 v v v的父亲上。不断进行以上操作,此时可能一个子树会形成树堆。对树上的每个结点 x x x,求出以 x x x为根的子树以这种方式形成的树堆中( x x x不可删除),结点最多的树堆的结点个数。
输入格式
第一行一个数 n n n,表示树上的点数。
第二行 n n n个数 a 0 , a 1 , … , a n − 1 a_0,a_1,\dots,a_{n-1} a0,a1,…,an−1, a i a_i ai表示 i i i的权值。
下面 n − 1 n-1 n−1行,每行两个数 x , y x,y x,y,表示 x , y x,y x,y之间有一条边。
树的根为 0 0 0。保证输入形成一棵树。
输出格式
一行 n n n个数,第 i i i个数表示以题目描述的方式生成的以 i − 1 i-1 i−1为根的数堆中,结点最多的树堆中的结点的个数。
样例输入
14
5 4 3 6 2 3 4 0 1 7 9 8 6 2
0 1
0 2
0 3
1 4
3 5
3 6
3 7
4 8
4 9
4 10
6 11
6 12
11 13
样例输出
9 3 1 5 2 1 2 1 1 1 1 2 1 1
数据范围
对于
20
%
20\%
20%的数据,
1
≤
n
≤
20
1\leq n\leq 20
1≤n≤20
对于
40
%
40\%
40%的数据,
1
≤
n
≤
5000
1\leq n\leq 5000
1≤n≤5000
对于
60
%
60\%
60%的数据,
1
≤
n
≤
5000
1\leq n\leq 5000
1≤n≤5000
对于
100
%
100\%
100%的数据,
1
≤
n
≤
100000
1\leq n\leq 100000
1≤n≤100000,
0
≤
a
i
≤
1000000000
0\leq a_i\leq 1000000000
0≤ai≤1000000000
题解
前置知识:线段树合并
这道题其实就是让我们求树上的最长上升子序列。
首先,我们先考虑一个序列的最长上升子序列。假设已经求出了前 i i i个数的最长上升子序列,在加入第 i + 1 i+1 i+1个数时,在当前数列中找到第一个大于等于它的数。如果有,则用新加入的数替换;否则将新加入的数放在队尾。
树上呢?也一样。用权值线段树维护每一个点,线段树的 s i z siz siz值就是该点所在子树中能选的最多的点的数量。
对于每个结点,先遍历其子树,然后将该结点的儿子结点的线段树合并到自己的线段树上。最后,在自己的线段树上找到第一个大于等于该结点的权值的位置。如果有,在权值线段树上这个位置减一,在该结点的权值所在位置加一;否则直接在该结点权值所在的位置加一。
最后,每个结点的答案即为该结点的权值线段树中小于等于该结点的权值的点的数量。时间复杂度为 O ( n log n ) O(n\log n) O(nlogn)。
code
#include<bits/stdc++.h>
using namespace std;
const int N=100000;
int n,x,y,tot=0,fl,a[100005],num[100005],d[200005],l[200005],r[200005],rt[100005],ans[100005];
struct node{
int lc,rc,s;
}tr[10000005];
void add(int xx,int yy){
l[++tot]=r[xx];d[tot]=yy;r[xx]=tot;
}
void merge(int &r1,int r2,int l,int r){
if(!r1||!r2){
r1=r1+r2;return;
}
if(l==r){
tr[r1].s+=tr[r2].s;return;
}
int mid=(l+r)/2;
merge(tr[r1].lc,tr[r2].lc,l,mid);
merge(tr[r1].rc,tr[r2].rc,mid+1,r);
tr[r1].s=tr[tr[r1].lc].s+tr[tr[r1].rc].s;
}
void pt(int &k,int l,int r,int z){
if(!k) k=++tot;
if(l==r){
++tr[k].s;return;
}
int mid=l+r>>1;
if(z<=mid) pt(tr[k].lc,l,mid,z);
else pt(tr[k].rc,mid+1,r,z);
tr[k].s=tr[tr[k].lc].s+tr[tr[k].rc].s;
}
void dele(int k,int l,int r,int z){
if(!k) return;
if(l==r){
if(tr[k].s>0){
--tr[k].s;fl=1;
}
return;
}
int mid=l+r>>1;
if(z<=mid){
dele(tr[k].lc,l,mid,z);
if(!fl) dele(tr[k].rc,mid+1,r,z);
}
else dele(tr[k].rc,mid+1,r,z);
tr[k].s=tr[tr[k].lc].s+tr[tr[k].rc].s;
}
int find(int k,int l,int r,int x,int y){
if(!k) return 0;
if(l>=x&&r<=y) return tr[k].s;
if(l>y||r<x) return 0;
if(l==r) return 0;
int mid=l+r>>1,re=0;
if(x<=mid) re+=find(tr[k].lc,l,mid,x,y);
if(y>mid) re+=find(tr[k].rc,mid+1,r,x,y);
return re;
}
void dfs(int u,int fa){
for(int i=r[u];i;i=l[i]){
if(d[i]==fa) continue;
dfs(d[i],u);
merge(rt[u],rt[d[i]],1,N);
}
fl=0;
dele(rt[u],1,N,a[u]+1);
pt(rt[u],1,N,a[u]);
ans[u]=find(rt[u],1,N,1,a[u]);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);num[i]=a[i];
}
sort(num+1,num+n+1);
int gs=unique(num+1,num+n+1)-num-1;
for(int i=1;i<=n;i++){
a[i]=lower_bound(num+1,num+gs+1,a[i])-num;
}
for(int i=1;i<n;i++){
scanf("%d%d",&x,&y);++x;++y;
add(x,y);add(y,x);
}
tot=0;
dfs(1,0);
for(int i=1;i<=n;i++){
printf("%d ",ans[i]);
}
return 0;
}
文章介绍了一个关于树堆的问题,其中涉及对有根树的节点进行特定操作,寻找最大树堆。解决方案利用线段树来维护每个节点的子树信息,通过合并和更新操作动态计算最长上升子序列,最终得到每个节点为根的最大树堆大小。
2008

被折叠的 条评论
为什么被折叠?



