【2019 NWERC B】Balanced Cut 题解

题目大意

  给定一棵 nn 个点的 AVL 树(点权恰好为 11nn),你需要选择其中的 kk 个点,满足:

  1. 如果要选一个点,那么它的祖先也必须选。也就是选出来的 kk 个点会组成一棵新的树。
  2. 这棵新的树也必须是 AVL 树。

  (放个传送门里面有图片可以看看例子

  每种选法可以表示为一个长度为 nn 的 01 串(表示每个点选或不选),你需要求出字典序最大的方案。

  1kn5×1051 \leq k \leq n \leq 5 \times 10^5
  4s,256MB

题解

  做这种全服个位数通过的题其实挺有快感的

  首先大的框架肯定是贪心,从小到大 check 每个点能不能选。

  check 的依据就是两点:会不会跟已选的点冲突,会不会必需结点数目超过 kk 的限制。AVL 树的一个性质是,树高是 O(logn)O(\log n) 的。基于这个我们可以设计出一个粗略的 check 的方法:
  (lson,rsonlson,rson 表示一个结点的左右儿子)
  设要 check ss 这个点,从 ss 开始不断往父亲跳,维护当前子树最少要选的点数 nownumnownum,以及所选的层数 nowdeepnowdeep。假设当前点是 xx,父亲为 yy
  若 xxyy 的右儿子,则 yy 的左儿子肯定已经全部考虑过了,定死了不能变了,所以只需判断这俩兄弟的层数差是否在 11 以内即可,然后更新 nowdeepnowdeepnownumnownum
  若 xxyy 的左儿子,则 yy 的右儿子肯定全都没考虑,我们预处理一个 dp 数组 fi,jf_{i,j} 表示以 ii 为根的子树选 jj 层的最少花费,那么这时只需要使 nownumnownum 加上 frsony,nowdeep1f_{rson_y,nowdeep-1} 即可。
  最后判断 nownumknownum \leq k 即是合法。

  会有一些问题:当 xxyy 左儿子时,yy 的右儿子可能曾经被 xx 子树里的其他结点提出过更大的层数要求,记为 tagrsonytag_{rson_y},是的这其实就是一个不断打 tag 的过程,所以实际上右儿子要选的层数应该是 frsony,max{nowdeep1,tagrsony}f_{rson_y,\max\{nowdeep-1,tag_{rson_y}\}}
  然后,当 check 一个点成功时,它必须沿着祖先链上去给所有右兄弟更新 tag。

  这时又会有新的问题:当前结点 xx 本身可能会有一个 tagxtag_x 的任务,然而我们为了使点数最少我们一直都只选必需的点,这导致 nowdeepnowdeep 可能完不成 tagxtag_x 这个任务,我们就需要计算现在 nowdeepnowdeep 层补到 tagxtag_x 层需要额外付出多少代价。
  记 hi(inowdeep)h_i (i \ge nowdeep) 表示当前子树从选 nowdeepnowdeep 层追加到选 ii 层需要额外补多少代价。这个东西也很好维护,当 nowdeepnowdeep 变大成 nowdeepnowdeep' 时,所有的 hi=hnowdeeph_i-=h_{nowdeep'};当往上跳遇到右兄弟时,再维护 hi=min{hi1+frson,ifrson,t, hi+frson,i1frson,t}h_i=\min\{h_{i-1}+f_{rson,i}-f_{rson,t},~h_i+f_{rson,i-1}-f_{rson,t}\}。(其中 t=max{nowdeep1,tagrson}t=\max\{nowdeep-1,tag_{rson}\},因为我们维护的是额外补的差价,而 frson,tf_{rson,t} 是已经付出的)

  时间复杂度:每个点往上跳是 O(logn)O(\log n) 步的,每跳一次维护一下 hh 数组又是 O(logn)O(\log n) 的,因此总的是 O(nlog2n)O(n \log^2 n)。但它跑得飞快,甚至这题其他人写的像是 O(nlogn)O(n \log n) 的(我都没看懂)用时是我的若干倍。。

  (另外有些小技巧,比如如果一个点它的(左)子树中有人确定要选了,那么它作为祖先就一定要选,就不用 check 了,这样的话任何要 check 的点都必然是左子树不选的,于是各变量的初值也方便多了。。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#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("");
}