思路:
首先看到题目不难想到直接枚举每一个点作为根进行DP,方程如下
其中 order 表示选择的顺序为 1,2,3,4…。
在此直接贪心让最大的
f
j
f_j
fj 匹配最小的
o
r
d
e
r
j
order_j
orderj即可。
这样直接做树形DP时间复杂度是
O
(
n
2
log
n
)
O(n^2 \log \ n )
O(n2log n)的,
但并不是本题的最优解。
(当然应为本题的
n
≤
1000
n \leq 1000
n≤1000,这样的算法可以通过)。
考虑如何优化:
我们若仔细思考上面的DP过程不难发现:
若对于
a
≠
b
a \not= b
a=b 有 a 做根和 b 做根时的
s
o
n
x
son_x
sonx相同 ,
那么我们就可以直接拿第一次计算出来的
f
x
f_x
fx直接作为返回值,
这样就可以减少许多运算量。
具体化上面的过程:对于每个相同的
s
o
n
x
son_x
sonx
他们都会对应相同的
f
a
t
h
e
r
x
father_x
fatherx ,可以用
d
p
[
x
]
[
f
a
t
h
e
r
x
]
dp[x][father_x]
dp[x][fatherx]记录答案,
这样记忆化搜索就完成了。
但是空间复杂度不够优,我们把
d
p
[
x
]
[
f
a
t
h
e
r
x
]
dp[x][father_x]
dp[x][fatherx]用
f
a
t
h
e
r
x
father_x
fatherx
连向 x 的单向边代替,这样空间只要开
O
(
n
)
O(n)
O(n) 即可。
来大致算一下这样的时间复杂度:
第一次DP时可以把 n-1条边的DP值纪录,显然一次DP的复杂度是
O
(
n
log
n
)
O(n \log n)
O(nlogn) ,然而我们只有 2n-2 条单向变,所以总时间大概相当于两次的第一次DP,复杂度仍然是
O
(
n
log
n
)
O(n \log n)
O(nlogn)。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
using namespace std;
const int N = 2e6 + 10;
struct edge {int next, to;} e[N * 2];
int n, head[N], tot, dp[N], ins[N], minn = 2147483647, a;
void add(int x, int y) {e[++tot] = (edge){head[x], y}; head[x] = tot;}
int dfs(int x, int f, int fr)
{
if(fr && dp[fr]) return dp[fr];
int res = 0;
priority_queue <int> q;
for(int i = head[x]; i; i = e[i].next)
{
int v = e[i].to;
if(v == f) continue;
q.push(dfs(v, x, i));
}
for(int i = 1; !q.empty(); i++, q.pop())
res = max(res, q.top() + i);
return dp[fr] = res;
}
int main()
{
freopen("news.in", "r", stdin);
freopen("news.out", "w", stdout);
scanf("%d", &n);
for(int i = 2; i <= n; i++)
scanf("%d", &a), add(a,i), add(i,a);
for(int i = 1; i <= n; i++)
{
ins[i] = dfs(i, 0, 0);
minn = min(minn, ins[i]);
}
printf("%d\n", minn + 1);
for(int i = 1; i <= n; i++) if(minn == ins[i]) printf("%d ",i);
}