0%

思路讲解

这道题目的想法实际上非常暴力。

DP状态定义

  • dp[u][h]:以节点u为根的子树,改造为正好高度h的AVL树的最小操作次数(代价)。
    • 注意:高度正好h,根u保留。
    • 如果无法做到,设为INF(大数)。

问题描述

这道题给你一棵以节点1为根的二叉树(节点数n ≤ 2×10^5),但它可能不是AVL树(即高度平衡二叉树:每个节点左右子树高度差不超过1)。你可以通过三种操作来改造它:

  1. 删除一个叶子节点。

  2. 选择一个没有左孩子的节点,添加一个新节点作为它的左孩子。

  3. 选择一个没有右孩子的节点,添加一个新节点作为它的右孩子。

目标是求最小操作次数,使整棵树变成AVL树。注意:操作可以任意次,包括删除原有节点或添加新节点,但根节点1必须保留(因为AVL树定义包括根)。

多测试用例,T ≤ 10000,但总∑n ≤ 2×10^5。

解题思路

我们用树形动态规划(DP)来解决这个问题。核心是计算每个子树的最小代价,使其成为指定高度的AVL树。

关键概念

  • AVL树的定义:如题所述,空树高度0;否则高度 = max(左高, 右高) + 1,且|左高 - 右高| ≤ 1,且左右子树也是AVL。

  • 操作的本质

    • 删除叶子:相当于 pruning(修剪)不需要的部分。
    • 添加孩子:相当于在空位上建新子树。
    • 要平衡树,我们可能需要删除整个子树(代价 = 子树大小,因为要逐个删除叶子),然后从头建一个平衡的子树(代价 = 新添加的节点数)。
  • 最****小节点数的AVL树:对于高度h的AVL树,最小节点数m[h]满足:

    • m[0] = 0(空树)
    • m[1] = 1(单节点)
    • m[h] = 1 + m[h-1] + m[h-2](类似斐波那契,最小化节点:一侧高度h-1,另一侧h-2)
    • 这可以预计算,h最高取60就够,因为m[60]已经很大,超过n的范围。

DP状态定义

  • dp[u][h]:以节点u为根的子树,改造为正好高度h的AVL树的最小操作次数(代价)。

    • 注意:高度正好h,根u保留。
    • 如果无法做到,设为INF(大数)。
  • 最终答案:min over h (dp[1][h]),因为根树高度可以任意,只要平衡。

最关键的这个部分已经给出

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
vector<vll> dp(N+5,vll(H+5,INF));
dp[0][0]=0;
dp[0][1]=1;
FOR(h,2,H){
dp[0][h]=dp[0][h-1]+dp[0][h-2]+1;
}
function<void (ll,ll)> dfs=[&](ll u,ll p){
bool isleaf=true;
vll wp;
for(auto v:g[u]){
if(v==p) continue;
dfs(v,u);
wp.EB(v);
isleaf=false;
}
if(isleaf){
dp[u][1]=0;
dp[u][0]=1;
FOR(h,2,H){
dp[u][h]=dp[0][h]-1;
}
return;
}
if(SZ(wp)==1){
wp.EB(0);
}
ll v=wp[0],v2=wp[1];
vll ldp(H+5,INF),lldp(H+5,INF);
FOR(h,1,H){
ldp[h]=dp[v][h-1];
}
FOR(h,1,H){
ldp[h]+=min({dp[v2][h-1],dp[v2][h-2<0?0:h-2]});
}
FOR(h,1,H){
lldp[h]=dp[v2][h-1];
}
FOR(h,1,H){
lldp[h]+=min({dp[v][h-1],dp[v][h-2<0?0:h-2]});
}
FOR(h,1,H){
dp[u][h]=min(ldp[h],lldp[h]);
}
dp[u][0]=dp[u][1]+1;

};
dfs(1,1);

AC代码

心路历程(WA,TLE,MLE……)

思路讲解

AC代码

https://www.luogu.com.cn/record/230330272

https://www.luogu.com.cn/record/230330447

最优解。

image

心路历程(WA,TLE,MLE……)

线段树内部之间不要相互调用

P1471 方差

千万不要这么干,因为不仅更新的时候要调用,下传标记也要调用,你能保证两个线段树的懒标记状态一样吗?这个是不可能的,因为你也不可能同时更新,询问两个线段树,除非把他们合为一个

image

思路讲解

暴力程序是这样的。

1
2
3
4
5
6
7
8
i128 ans=0;
while(true){
if(n<W) break;
ans+=(1+N)*N/2-(sum+1)*sum/2;
sum+=n/W;
n-=n/W;
}
ans+=(1+N)*N/2-(sum+1)*sum/2;

不难发现,我们可以分块处理相同n/W的部分,以获得logn的时间复杂度。

AC代码

https://acm.hdu.edu.cn/contest/view-code?cid=1179&rid=14726

心路历程(WA,TLE,MLE……)

容易发现,fob的计算一定是交给后面算的,就算是最后一个也是。

image

思路讲解

我的做法还是我的做法,就是需要用zkw线段树卡一下常

复杂度是O(k×N×log(n))O(\sqrt k \times N \times \log(n))

AC代码

https://acm.hdu.edu.cn/contest/status?cid=1178&rid=15857

稍微优化过的版本,不过这两个都比较卡着这个时限。

https://acm.hdu.edu.cn/contest/view-code?cid=1178&rid=15813

自己写的线段树rmq,会比之前的快一点。

https://acm.hdu.edu.cn/contest/view-code?cid=1178&rid=19430

心路历程(WA,TLE,MLE……)