原文首发自彼得攀的小站
本次的A题比较难,应该算leetcode中的Hard+,类似leetcode 265,但会更难一点:leetcode 265用二维dp即可,而A题需要三维dp
A. Flattening
Solution
这道题比较难,做的时候感觉DP不易解,用了贪心,3个小时有一个半小时都在调这个,最后还是没调出来…
后面看了官方题解,果然还是用dp求解
解法
定义 w a l l i wall_i walli代表第i面墙的高度;定义所有的墙有t种高度,用 n u m [ i ] , i ∈ [ 0 , t ) num[i], i\in [0, t) num[i],i∈[0,t)来表示
看到题,比较容易想到尝试动态规划,二维的状态容易被想到:定义 d p [ i ] [ j ] dp[i][j] dp[i][j]代表前 i i i个元素,至多包含 j j j对不相同的相同的相邻数字时需要的最少改变次数。此时注意二维很难做状态转移:对于前 i − 1 i-1 i−1个元素,再加入一个元素时,由 w a l l i − 1 wall_{i-1} walli−1和 w a l l i wall_{i} walli的取值,可以有多个状态转移的过程,很难分析,所以需要三维状态
令
d
p
[
i
]
[
j
]
[
m
]
dp[i][j][m]
dp[i][j][m]代表前
i
i
i个元素,至多包含
j
j
j对不相同的相邻数字, 且其第
i
i
i个元素为
n
u
m
[
m
]
num[m]
num[m]时需要的最少改变次数,此时所求解为:
m
i
n
(
d
p
[
n
]
[
j
]
[
m
]
)
,
f
o
r
j
∈
[
0
,
k
]
a
n
d
m
∈
[
0
,
t
)
min(dp[n][j][m]), for\ j\in[0, k]\ and\ m\in[0, t)
min(dp[n][j][m]),for j∈[0,k] and m∈[0,t)
状态转移方程为:
d
p
[
i
]
[
j
]
[
m
]
=
m
i
n
{
m
i
n
(
d
p
[
i
−
1
]
[
j
]
[
m
]
,
d
p
[
i
−
1
]
[
j
−
1
]
[
n
(
n
≠
m
)
]
)
w
a
l
l
i
=
=
n
u
m
[
m
]
m
i
n
(
1
+
d
p
[
i
−
1
]
[
j
−
1
]
[
n
(
n
≠
m
)
]
,
1
+
d
p
[
i
−
1
]
[
j
]
[
m
]
)
w
a
l
l
i
≠
n
u
m
[
m
]
dp[i][j][m]=min \begin{cases} min(dp[i-1][j][m], dp[i-1][j-1][n(n\neq m)])& \text{ $wall_{i}== num[m]$}\\ min(1+dp[i-1][j-1][n(n\neq m)], 1+dp[i-1][j][m])& \text{$wall_i\neq num[m]$}\\ \end{cases}
dp[i][j][m]=min{min(dp[i−1][j][m],dp[i−1][j−1][n(n=m)])min(1+dp[i−1][j−1][n(n=m)],1+dp[i−1][j][m]) walli==num[m]walli=num[m]
上述过程中:
- 当 w a l l i = n u m [ m ] wall_{i}= num[m] walli=num[m]时,有两种情况: w a l l i − 1 = w a l l i = n u m [ m ] wall_{i-1}=wall_i=num[m] walli−1=walli=num[m],不会增加不一致的对数,所以为 d p [ i − 1 ] [ j ] [ m ] dp[i-1][j][m] dp[i−1][j][m]; w a l l i − 1 ≠ w a l l i wall_{i-1}\neq wall_i walli−1=walli, 不一致的对数会加一(即前i-1个元素中,最多j-1个不一致),所以是 d p [ i − 1 ] [ j − 1 ] [ n ( n ≠ m ) ] dp[i-1][j-1][n(n\neq m)] dp[i−1][j−1][n(n=m)]
- 当 w a l l i ≠ n u m [ m ] wall_{i}\neq num[m] walli=num[m]时, 代表第i个数一定会改变一次,所以需要在之前的状态加一
定义
m
i
n
D
[
i
]
[
j
]
=
m
i
n
(
d
p
[
i
]
[
j
]
[
l
]
)
minD[i][j]=min(dp[i][j][l])
minD[i][j]=min(dp[i][j][l]), 其中
l
∈
[
0
,
t
)
l\in [0, t)
l∈[0,t), 上述状态转移方程可以简化为:
d
p
[
i
]
[
j
]
[
m
]
=
m
i
n
(
d
p
[
i
−
1
]
[
j
]
[
m
]
,
m
i
n
D
[
i
−
1
]
[
j
−
1
]
)
+
(
w
a
l
l
i
!
=
n
u
m
[
m
]
)
dp[i][j][m]=min(dp[i-1][j][m], minD[i-1][j-1])+(wall_i!=num[m])
dp[i][j][m]=min(dp[i−1][j][m],minD[i−1][j−1])+(walli!=num[m])
初始状态:
d
p
[
1
]
[
0
]
[
m
]
=
{
0
w
a
l
l
1
=
=
n
u
m
[
m
]
1
w
a
l
l
1
≠
n
u
m
[
m
]
dp[1][0][m]= \begin{cases} 0& \text{ $wall_{1}== num[m]$}\\ 1& \text{$wall_1\neq num[m]$}\\ \end{cases}
dp[1][0][m]={01 wall1==num[m]wall1=num[m]
且
m
i
n
D
[
1
]
[
0
]
=
0
minD[1][0]=0
minD[1][0]=0。
复杂度分析
分析见上述状态转移方程, O ( N 2 K ) O(N^2K) O(N2K) time
代码
B. Teach Me
Solution
解法
题目求的是(i, j)的个数,其中员工i能教j。令 m ( i ) m(i) m(i)代表员工i能指导的员工数,则所求结果为 ∑ i n m ( i ) \sum_i^n{m(i}) ∑inm(i)。注意求 m ( i ) m(i) m(i)比较麻烦,令 m ′ ( i ) m'(i) m′(i)代表i不能指导的员工数,则 m ( i ) = n − m ′ ( i ) m(i)=n-m'(i) m(i)=n−m′(i)。这里引入 m ′ ( i ) m'(i) m′(i),是因为 m ′ ( i ) m'(i) m′(i)比 m ( i ) m(i) m(i)更易求:当且仅员工j的技能包含员工i的所有技能时,员工i不能指导j,。(思想就类似概率论中,事件A发生的概率不易求,尝试求1-P(A))
可以利用二进制来枚举一个员工技能的子集,最多2^5个,统计所有员工技能的子集出现情况。之后就可以直接求出 m ′ ( i ) m'(i) m′(i)
note:集合可以hash为一个数,然后利用hash table来存储。
复杂度分析
O ( 2 5 ∗ n ) O(2^5*n) O(25∗n) time.
代码
C. Spectating Villages
Solution
解法
Test 1的暴力枚举在这里
题目中的村庄构成了一个树,将任意一个村庄作为根节点,即可将该树转化为一个有根树,对村庄从0到V-1编号,不妨设村庄0为根。
每个村庄都有三个状态:建灯塔且亮、不建灯塔且亮、不建灯塔且不亮。可以用树形dp来枚举这三个状态,令 c h i l d ( i ) child(i) child(i)代表 i i i的子节点集合, v a l u e s ( i ) values(i) values(i)代表 i i i的权值:
-
d
p
[
i
]
[
0
]
=
∑
j
∈
c
h
i
l
d
(
i
)
m
a
x
(
d
p
[
j
]
[
0
]
,
d
p
[
j
]
[
1
]
)
dp[i][0]=\sum_{j\in child(i)}max(dp[j][0], dp[j][1])
dp[i][0]=∑j∈child(i)max(dp[j][0],dp[j][1]):
村庄i不建灯塔且不亮,此时所有的子节点不能建灯塔 -
d
p
[
i
]
[
1
]
=
{
∑
j
∈
c
h
i
l
d
(
i
)
m
a
x
(
d
p
[
j
]
[
0
]
,
d
p
[
j
]
[
1
]
,
d
p
[
j
]
[
2
]
)
∃
j
∈
c
h
i
l
d
(
i
)
,
d
p
[
j
]
[
2
]
≥
d
p
[
j
]
[
0
]
+
d
p
[
j
]
[
1
]
∑
j
∈
c
h
i
l
d
(
i
)
m
a
x
(
d
p
[
j
]
[
0
]
,
d
p
[
j
]
[
1
]
,
d
p
[
j
]
[
2
]
)
+
arg
max
j
∈
c
h
i
l
d
(
i
)
(
d
p
[
j
]
[
2
]
−
d
p
[
j
]
[
0
]
,
d
p
[
j
]
[
2
]
−
d
p
[
j
]
[
1
]
)
e
l
s
e
dp[i][1]= \begin{cases}\sum_{j\in child(i)}max(dp[j][0], dp[j][1], dp[j][2])& \exists{j\in child(i)}, dp[j][2]\geq dp[j][0]+dp[j][1]\\ \sum_{j\in child(i)}max(dp[j][0], dp[j][1], dp[j][2])+\arg \max_{j\in child(i)}(dp[j][2]-dp[j][0],dp[j][2]-dp[j][1]) & else \end{cases}
dp[i][1]={∑j∈child(i)max(dp[j][0],dp[j][1],dp[j][2])∑j∈child(i)max(dp[j][0],dp[j][1],dp[j][2])+argmaxj∈child(i)(dp[j][2]−dp[j][0],dp[j][2]−dp[j][1])∃j∈child(i),dp[j][2]≥dp[j][0]+dp[j][1]else
村庄i不建灯塔且亮,此时子节点至少有一个建灯塔(此时取子树权值之和最大的情况,即选择子树亮灯和不亮之间最大,注意至少有一个子树需要亮灯) -
d
p
[
i
]
[
2
]
=
v
a
l
u
e
s
[
i
]
+
∑
j
∈
c
h
i
l
d
(
i
)
m
a
x
(
d
p
[
j
]
[
1
]
,
d
p
[
j
]
[
2
]
)
dp[i][2]=values[i]+\sum_{j\in child(i)}max(dp[j][1], dp[j][2])
dp[i][2]=values[i]+∑j∈child(i)max(dp[j][1],dp[j][2])
村庄i建灯塔且亮,此时子节点可以不建灯塔,或是建任意多的灯塔,但是所有的子节点均被点亮,根节点会影响子节点的状态:若子节点未建灯塔且不亮,其状态会变为亮,即 d p [ j ] [ 0 ] 变 为 d p [ j ] [ 0 ] + v a l u e s [ j ] dp[j][0]变为dp[j][0]+values[j] dp[j][0]变为dp[j][0]+values[j]
由于知道根节点,可以从上到下递归,所求结果为 m a x ( d p [ 0 ] [ 0 ] , d p [ 0 ] [ 1 ] , d p [ 0 ] [ 2 ] ) max(dp[0][0], dp[0][1], dp[0][2]) max(dp[0][0],dp[0][1],dp[0][2]),dp的初始状态为叶子节点的状态:
- d p [ l e a f ] [ 0 ] = 0 dp[leaf][0]=0 dp[leaf][0]=0
- d p [ l e a f ] [ 1 ] = I N T _ M I N dp[leaf][1]=INT\_MIN dp[leaf][1]=INT_MIN: 对于叶子节点,该状态并没有什么实际上的意义
- d p [ l e a f ] [ 2 ] = v a l u e s l e a f dp[leaf][2]=values_{leaf} dp[leaf][2]=valuesleaf
复杂度分析
解法中,利用图的DFS来模拟有根树的遍历过程,所以时间复杂度为: O ( n ) O(n) O(n) time.