证明:有依赖背包结点数优化后为 O ( n 2 ) O(n^2) O(n2)
siz[u]
表示以u为根的树的结点数
深搜过程中,siz[u]
表示根结点为u的树的根结点加上前i个子树的结点数。
- 根结点为u的树取前i个子树的结点,取到的结点数量小于等于
siz[u]
- 根结点为v的子树取到的结点数量小于等于
siz[v]
例:
洛谷 P2014 [CTSC1997] 选课 使用结点数优化
#include<bits/stdc++.h>
using namespace std;
#define N 305
int n, m, w[N], siz[N], dp[N][N];//dp[u][i][j]:结点u的前i个孩子,最多选择j门课能获得的最大学分
vector<int> edge[N];
void dfs(int u)
{
dp[u][1] = w[u];//选1门课,就只能选自己
siz[u] = 1;
for(int v : edge[u])
{
dfs(v);
siz[u] += siz[v];
for(int j = min(m, siz[u]); j >= 1; --j)//在树中选j门课
for(int k = 0; k < j && k <= siz[v]; ++k)//在子树v中选了k门课 (因为还要选v,最多选j-1门)
dp[u][j] = max(dp[u][j], dp[u][j-k]+dp[v][k]);
}
}
int main()
{
int f;
cin >> n >> m;
m++;//算上0号结点
for(int i = 1; i <= n; ++i)
{
cin >> f >> w[i];
edge[f].push_back(i);
}
dfs(0);
cout << dp[0][m];
return 0;
}
假设m很大,不考虑j的影响,其核心代码可以视作:
void dfs(int u)
{
dp[u][1] = w[u];//选1门课,就只能选自己
siz[u] = 1;
for(int v : edge[u])
{
dfs(v);
siz[u] += siz[v];
for(int j = siz[u]; j >= 1; --j)
for(int k = 0; <= siz[v]; ++k)
dp[u][j] = max(dp[u][j], dp[u][j-k]+dp[v][k]);
}
}
设根结点为u的树的结点数量为n,
证明:上述代码的复杂度为
O
(
n
2
)
O(n^2)
O(n2)
设结点u有k个子树1、2、3、…、k
每个子树的结点数分别为
a
1
,
a
2
,
.
.
.
,
a
k
a_1, a_2, ..., a_k
a1,a2,...,ak,因此有
a
1
+
a
2
+
.
.
.
+
a
k
=
n
−
1
a_1+a_2+...+a_k = n-1
a1+a2+...+ak=n−1
该代码中,双重循环内层语句的运行次数近似为:
a
1
∗
a
1
+
(
a
1
+
a
2
)
∗
a
2
+
(
a
1
+
a
2
+
a
3
)
∗
a
3
+
.
.
.
+
(
a
1
+
.
.
.
+
a
k
)
∗
a
k
a_1*a_1+(a_1+a_2)*a_2+(a_1+a_2+a_3)*a_3+...+(a_1+...+a_k)*a_k
a1∗a1+(a1+a2)∗a2+(a1+a2+a3)∗a3+...+(a1+...+ak)∗ak
使用数学归纳法:
- 如果结点u的孩子都是叶子结点,则 a 1 , a 2 , . . . , a k a_1, a_2, ..., a_k a1,a2,...,ak都为1,那么语句运行次数为: 1 + 2 + . . . + k = ( 1 + k ) ⋅ k / 2 ≤ k 2 1+2+...+k =(1+k)\cdot k/2 \le k^2 1+2+...+k=(1+k)⋅k/2≤k2
- 如果结点u的孩子不都是叶子结点,对于子树x的递归调用,语句运行次数
≤
a
x
2
\le a_x^2
≤ax2,因此总语句运行次数
≤
a
1
∗
a
1
+
(
a
1
+
a
2
)
∗
a
2
+
(
a
1
+
a
2
+
a
3
)
∗
a
3
+
.
.
.
+
(
a
1
+
.
.
.
+
a
k
)
∗
a
k
+
a
1
2
+
a
2
2
+
.
.
.
+
a
k
2
\le a_1*a_1+(a_1+a_2)*a_2+(a_1+a_2+a_3)*a_3+...+(a_1+...+a_k)*a_k+a_1^2+a_2^2+...+a_k^2
≤a1∗a1+(a1+a2)∗a2+(a1+a2+a3)∗a3+...+(a1+...+ak)∗ak+a12+a22+...+ak2
将前面每项写开
a 1 2 a_1^2 a12
a 1 ∗ a 2 + a 2 2 a_1*a_2+a_2^2 a1∗a2+a22
a 1 ∗ a 3 + a 2 ∗ a 3 + a 3 2 a_1*a_3+a_2*a_3+a_3^2 a1∗a3+a2∗a3+a32
…
a 1 ∗ a k + . . . + a k 2 a_1*a_k+...+a_k^2 a1∗ak+...+ak2
相加得
a 1 ∗ ∑ i = 1 k a i + a 2 ∗ ∑ i = 2 k a i + a 3 ∗ ∑ i = 3 k a i + . . . + a k ∗ a k < a_1*\sum_{i=1}^k{a_i}+a_2*\sum_{i=2}^k{a_i}+a_3*\sum_{i=3}^k{a_i}+...+a_k*a_k < a1∗∑i=1kai+a2∗∑i=2kai+a3∗∑i=3kai+...+ak∗ak<
a 1 ∗ ∑ i = 1 k a i + a 2 ∗ ∑ i = 1 k a i + a 3 ∗ ∑ i = 1 k a i + . . . + a k ∗ ∑ i = 1 k a i = ( ∑ i = 1 k a i ) 2 = ( a 1 + a 2 + . . . + a k ) 2 = ( n − 1 ) 2 < n 2 a_1*\sum_{i=1}^k{a_i}+a_2*\sum_{i=1}^k{a_i}+a_3*\sum_{i=1}^k{a_i}+...+a_k*\sum_{i=1}^k{a_i} = (\sum_{i=1}^k{a_i})^2 = (a_1+a_2+...+a_k)^2=(n-1)^2<n^2 a1∗∑i=1kai+a2∗∑i=1kai+a3∗∑i=1kai+...+ak∗∑i=1kai=(∑i=1kai)2=(a1+a2+...+ak)2=(n−1)2<n2
而 a 1 2 + a 2 2 + . . . + a k 2 < ( a 1 + a 2 + . . . + a k ) 2 = ( n − 1 ) 2 < n 2 a_1^2+a_2^2+...+a_k^2 < (a_1+a_2+...+a_k)^2=(n-1)^2<n^2 a12+a22+...+ak2<(a1+a2+...+ak)2=(n−1)2<n2
因此语句总运行次数 < 2 n 2 <2n^2 <2n2
因此该算法的时间复杂度为 O ( n 2 ) O(n^2) O(n2)