C dfs序
题目:
思路:这是一道推公式的贪心题,不难发现,一个根节点子树的遍历顺序与子树的大小与权值之和有关,显而易见的应该用这两个关键字推导公式。首先关于pi * wi,这里直接求出每个点的遍历次序显然是不可能的,这里可以应用递推的思想把这个遍历次序转换,
例如一颗如图所示的树
这里先不考虑权值,只考虑如何将pi * wi转化,令w为子树的点权之和,_size为子树大小,均不包含根节点
最开始以1为根节点的子树先遍历2节点,此时2节点的带来的贡献w[2] * 1, 3节点带来的贡献就是w[3] * (_size[2] +1)。注意到3是整棵树最后遍历的,并且_size[2] + 1为4,符合遍历顺序(因为这里没考虑根节点,因此所有点的遍历顺序都比实际少1,只需要在最后求解时加上w[1]修正即可)此时接着遍历2节点,只能遍历到4,4(子树)的贡献为w[4] * 1,此时4这个点的权值加了两遍符合顺序。。。以此类推。为什么要转化这个pi * wi,因为只有转化后方便我们做递归操作
接着开始寻找遍历顺序,顺着上面的思想推个公式假设节点x有n颗子树,为了简化问题,这里n取2(其实是不会证n...一堆式子看着就头疼)分别为1 2设先计算1为最优解 则有 w[1] * 1 + w[2] * (_size[1] + 1) > w[2] * 1 + w[1] * (_size[2] + 1) 化简 w[2] * _size[1] > w[1] * _size[2]递推式子就出来了...只要在dfs时,对于每个u临边遍历完之后对其临边按照这个式子sort即可
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<LL> a(n + 1);
vector<vector<int>> adj((n + 1));
for(int i = 1; i <= n; i ++ ) cin >> a[i];
for(int i = 2; i <= n; i ++ ) {
int x;
cin >> x;
adj[x].push_back(i);
}
vector<LL> _size(n + 1);
vector<LL> w(n + 1);
function<void(int)> dfs = [&](int u) -> void {
_size[u] = 1;
w[u] = a[u];
for(auto t: adj[u]) {
dfs(t);
_size[u] += _size[t];
w[u] += w[t];
}
sort(adj[u].begin(), adj[u].end(), [&](int a, int b) -> bool {
return w[a] * _size[b] < _size[a] * w[b];
});
};
dfs(1);
LL ans = 0;
function<void(int)> dfss = [&](int u) -> void {
int sum = 0;
for(auto t: adj[u]) {
dfss(t);
ans += w[t] * (sum + 1);
sum += _size[t];
}
};
dfss(1);
cout << ans + w[1];
return 0;
}