题目大意
给定一棵 n n n 个点的 AVL 树(点权恰好为 1 1 1 到 n n n),你需要选择其中的 k k k 个点,满足:
- 如果要选一个点,那么它的祖先也必须选。也就是选出来的 k k k 个点会组成一棵新的树。
- 这棵新的树也必须是 AVL 树。
(放个传送门里面有图片可以看看例子
每种选法可以表示为一个长度为 n n n 的 01 串(表示每个点选或不选),你需要求出字典序最大的方案。
1
≤
k
≤
n
≤
5
×
1
0
5
1 \leq k \leq n \leq 5 \times 10^5
1≤k≤n≤5×105
4s,256MB
\\
\\
\\
题解
做这种全服个位数通过的题其实挺有快感的
首先大的框架肯定是贪心,从小到大 check 每个点能不能选。
check 的依据就是两点:会不会跟已选的点冲突,会不会必需结点数目超过
k
k
k 的限制。AVL 树的一个性质是,树高是
O
(
log
n
)
O(\log n)
O(logn) 的。基于这个我们可以设计出一个粗略的 check 的方法:
(
l
s
o
n
,
r
s
o
n
lson,rson
lson,rson 表示一个结点的左右儿子)
设要 check
s
s
s 这个点,从
s
s
s 开始不断往父亲跳,维护当前子树最少要选的点数
n
o
w
n
u
m
nownum
nownum,以及所选的层数
n
o
w
d
e
e
p
nowdeep
nowdeep。假设当前点是
x
x
x,父亲为
y
y
y。
若
x
x
x 是
y
y
y 的右儿子,则
y
y
y 的左儿子肯定已经全部考虑过了,定死了不能变了,所以只需判断这俩兄弟的层数差是否在
1
1
1 以内即可,然后更新
n
o
w
d
e
e
p
nowdeep
nowdeep 和
n
o
w
n
u
m
nownum
nownum;
若
x
x
x 是
y
y
y 的左儿子,则
y
y
y 的右儿子肯定全都没考虑,我们预处理一个 dp 数组
f
i
,
j
f_{i,j}
fi,j 表示以
i
i
i 为根的子树选
j
j
j 层的最少花费,那么这时只需要使
n
o
w
n
u
m
nownum
nownum 加上
f
r
s
o
n
y
,
n
o
w
d
e
e
p
−
1
f_{rson_y,nowdeep-1}
frsony,nowdeep−1 即可。
最后判断
n
o
w
n
u
m
≤
k
nownum \leq k
nownum≤k 即是合法。
会有一些问题:当
x
x
x 是
y
y
y 左儿子时,
y
y
y 的右儿子可能曾经被
x
x
x 子树里的其他结点提出过更大的层数要求,记为
t
a
g
r
s
o
n
y
tag_{rson_y}
tagrsony,是的这其实就是一个不断打 tag 的过程,所以实际上右儿子要选的层数应该是
f
r
s
o
n
y
,
max
{
n
o
w
d
e
e
p
−
1
,
t
a
g
r
s
o
n
y
}
f_{rson_y,\max\{nowdeep-1,tag_{rson_y}\}}
frsony,max{nowdeep−1,tagrsony}。
然后,当 check 一个点成功时,它必须沿着祖先链上去给所有右兄弟更新 tag。
这时又会有新的问题:当前结点
x
x
x 本身可能会有一个
t
a
g
x
tag_x
tagx 的任务,然而我们为了使点数最少我们一直都只选必需的点,这导致
n
o
w
d
e
e
p
nowdeep
nowdeep 可能完不成
t
a
g
x
tag_x
tagx 这个任务,我们就需要计算现在
n
o
w
d
e
e
p
nowdeep
nowdeep 层补到
t
a
g
x
tag_x
tagx 层需要额外付出多少代价。
记
h
i
(
i
≥
n
o
w
d
e
e
p
)
h_i (i \ge nowdeep)
hi(i≥nowdeep) 表示当前子树从选
n
o
w
d
e
e
p
nowdeep
nowdeep 层追加到选
i
i
i 层需要额外补多少代价。这个东西也很好维护,当
n
o
w
d
e
e
p
nowdeep
nowdeep 变大成
n
o
w
d
e
e
p
′
nowdeep'
nowdeep′ 时,所有的
h
i
−
=
h
n
o
w
d
e
e
p
′
h_i-=h_{nowdeep'}
hi−=hnowdeep′;当往上跳遇到右兄弟时,再维护
h
i
=
min
{
h
i
−
1
+
f
r
s
o
n
,
i
−
f
r
s
o
n
,
t
,
h
i
+
f
r
s
o
n
,
i
−
1
−
f
r
s
o
n
,
t
}
h_i=\min\{h_{i-1}+f_{rson,i}-f_{rson,t},~h_i+f_{rson,i-1}-f_{rson,t}\}
hi=min{hi−1+frson,i−frson,t, hi+frson,i−1−frson,t}。(其中
t
=
max
{
n
o
w
d
e
e
p
−
1
,
t
a
g
r
s
o
n
}
t=\max\{nowdeep-1,tag_{rson}\}
t=max{nowdeep−1,tagrson},因为我们维护的是额外补的差价,而
f
r
s
o
n
,
t
f_{rson,t}
frson,t 是已经付出的)
时间复杂度:每个点往上跳是 O ( log n ) O(\log n) O(logn) 步的,每跳一次维护一下 h h h 数组又是 O ( log n ) O(\log n) O(logn) 的,因此总的是 O ( n log 2 n ) O(n \log^2 n) O(nlog2n)。但它跑得飞快,甚至这题其他人写的像是 O ( n log n ) O(n \log n) O(nlogn) 的(我都没看懂)用时是我的若干倍。。
(另外有些小技巧,比如如果一个点它的(左)子树中有人确定要选了,那么它作为祖先就一定要选,就不用 check 了,这样的话任何要 check 的点都必然是左子树不选的,于是各变量的初值也方便多了。。
代码
#include<bits/stdc++.h>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
typedef long long LL;
const int maxn=5e5+5, MX=30;
const int inf=522133279;
int n,k,root,son[maxn][2],p[maxn];
int f[maxn][MX+2],nmax[maxn],deep[maxn];
void dfs_dp(int k)
{
nmax[k]=k;
int l=son[k][0], r=son[k][1];
if (l)
{
deep[l]=deep[k]+1;
dfs_dp(l);
}
if (r)
{
deep[r]=deep[k]+1;
dfs_dp(r);
nmax[k]=max(nmax[k],nmax[r]);
}
f[k][1]=1;
fo(j,2,MX) f[k][j]=min(f[k][j],min(f[l][j-1]+f[r][j-2],f[l][j-2]+f[r][j-1])+1);
}
int tag[maxn],maxdeep[maxn],num[maxn],h[MX+2];
void Make_tag(int st)
{
for(int x=st; x!=-1; x=p[x])
{
maxdeep[x]=max(maxdeep[x],deep[st]);
num[x]++;
int y=p[x];
if (y!=-1 && son[y][0]==x) tag[son[y][1]]=max(tag[son[y][1]],maxdeep[x]-1-deep[y]);
}
}
bool check(int x)
{
if (num[x]) return 1;
int nowdeep=0, nownum=0;
memset(h,31,sizeof(h));
h[0]=f[son[x][1]][0], h[1]=f[son[x][1]][1];
for(; x!=-1; x=p[x])
{
int y=p[x];
nownum++;
if (nowdeep+1<tag[x])
{
nownum+=h[nowdeep=tag[x]-1];
if (nownum>=inf) return 0;
fd(i,MX,nowdeep) if (h[i]<inf) h[i]-=h[nowdeep];
}
nowdeep++;
fd(i,MX,nowdeep) h[i]=h[i-1];
if (y!=-1 && son[y][0]==x && son[y][1])
{
int r=son[y][1], t=max(tag[r],nowdeep-1);
if (f[r][t]>=inf) return 0;
nownum+=f[r][t];
fd(i,MX,nowdeep+1)
{
h[i]=min(h[i-1]+f[r][i]-f[r][t],h[i]+f[r][i-1]-f[r][t]);
if (h[i]>n) h[i]=inf;
}
h[nowdeep]=0;
} else if (y!=-1 && son[y][1]==x && son[y][0])
{
int l=son[y][0];
if (nowdeep-(maxdeep[l]-deep[y])>1) return 0;
nowdeep=max(nowdeep,maxdeep[l]-deep[y]);
h[nowdeep]=0;
nownum+=num[son[y][0]];
}
}
return nownum<=k;
}
bool ans[maxn];
int main()
{
scanf("%d %d",&n,&k);
fo(i,1,n)
{
scanf("%d",&p[i]);
if (p[i]==-1) root=i; else son[p[i]][(i>p[i])]=i;
}
memset(f,31,sizeof(f));
fo(i,0,n) f[i][0]=0;
deep[root]=1;
dfs_dp(root);
fo(i,1,n) if (check(i))
{
ans[i]=1;
Make_tag(i);
}
fo(i,1,n) putchar(ans[i] ?'1' :'0');
puts("");
}