换根DP
距离和
题解
我们考虑先计算以\(1\)为根时\(1\)到其他所有点的距离和
我们定义\(sz_u\)为以\(u\)为根的子树中点的数量, \(dp1_u\)表示以\(u\)为根的子树中的点到\(u\)的距离和
我们可以通过第一次\(dfs\)求出\(sz_u,dp1_u\)
\[sz_u += sz_v + 1\ (u本身) \\ dp1_u += dp1_v + sz_v \]我们定义\(dp2_u\)为\(u\)的父亲作为其子树时的贡献,\(dp2_1 = 0\);\(ans_u\)表示以\(u\)为根时,其他所有点到\(u\)点的距离和
容易得到\(ans_u = dp1_u + dp2_u\)
我们考虑如何计算\(dp2_u\)
我们可以通过第一次\(dfs\)求出\(dp2_u\)
\[dp2_v = ans_u - (dp1_v + sz_v) + (n - sz_v),v是u的儿子 \]时间复杂度:\(O(n)\)
const int N = 2e5 + 10, M = 4e5 + 10;
int n;
vector<int> g[N];
int sz[N], dp1[N], dp2[N], ans[N];
void dfs1(int u, int par)
{
sz[u] = 1;
for (auto v : g[u])
{
if (v == par)
continue;
dfs1(v, u);
dp1[u] += dp1[v] + sz[v];
sz[u] += sz[v];
}
}
void dfs2(int u, int par)
{
ans[u] = dp1[u] + dp2[u];
for (auto v : g[u])
{
if (v == par)
continue;
dp2[v] = ans[u] - (dp1[v] + sz[v]) + (n - sz[v]);
dfs2(v, u);
}
}
void solve()
{
cin >> n;
for (int i = 1; i < n; ++i)
{
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
dfs1(1, 0);
dfs2(1, 0);
for (int i = 1; i <= n; ++i)
cout << ans[i] << endl;
}
Accumulation Degree
题解
我们考虑先计算\(1\)作为根的时候整个水系的流量
我们定义\(dp1_u\)代表以\(u\)为根的子树中的流量和
所以我们可以通过第一次\(dfs\)求出\(dp1_u\)
\[\begin{equation} \left\{ \begin{array}{lr} dp1_u = 0,u是叶子节点\\ dp1_u = \sum min(dp1_v, w_{u,v}), v是u的儿子 \end{array} \right. \end{equation} \]我们定义\(dp2_u\)代表\(u\)的父亲作为\(u\)的子树时对流量产生的贡献,\(ans_u\)为以\(u\)为源点时整个水系的流量
容易得到\(ans_u = dp1_u + dp2_u\)
我们考虑如何计算\(dp2_u\)
我们可以通过第二次\(dfs\)求出\(dp2_u\), \(dp2_1 = 0\)
\[dp2_v = min(ans_u - min(dp1_v, w_{u, v}), w_{u, v}) \]时间复杂度:\(O(n)\)
const int N = 2e5 + 10, M = 4e5 + 10;
int n;
vector<pair<int, int>> g[N];
int dp1[N], dp2[N], ans[N], sz[N];
void dfs1(int u, int par)
{
sz[u] = 1;
for (auto [v, w] : g[u])
{
if (v == par)
continue;
dfs1(v, u);
sz[u] += sz[v];
if (sz[v] == 1)
dp1[u] += w;
else
dp1[u] += min(dp1[v], w);
}
}
void dfs2(int u, int par)
{
ans[u] = dp1[u] + dp2[u];
for (auto [v, w] : g[u])
{
if (v == par)
continue;
dp2[v] = min(ans[u] - min(dp1[v], w), w);
dfs2(v, u);
}
}
void solve()
{
cin >> n;
for (int i = 1; i <= n; ++i)
{
g[i].clear();
ans[i] = 0;
dp1[i] = dp2[i] = 0;
}
for (int i = 1; i < n; ++i)
{
int u, v, w;
cin >> u >> v >> w;
g[u].push_back({v, w});
g[v].push_back({u, w});
}
dfs1(1, 0);
dfs2(1, 0);
int mx = 0;
for (int i = 1; i <= n; ++i)
mx = max(mx, ans[i]);
cout << mx << endl;
}
树的中心
树的中心:在树中找到一个点,他到其他所有点中的最远距离最近
显然一个点\(u\)到它的所有点中的最大距离由两部分组成:以\(u\)为根节点到子树中的最大距离(向下最大距离)和与\(u\)为起点到子树外的最大距离(向上最大距离)
\(dp[u][0]\):从\(u\)到子树中的最大距离
\(dp[u][1]\):从\(u\)到子树中的次大距离
\(dp[u][2]\):从\(u\)到子树外的最大距离
为什么我们需要一个次大距离?
因为以\(v\)为起点到其子树外的最大距离,首先它的父节点是\(u\),所以既有可能往上走(\(dp[u][2]+w\)),如果往下走距离更大,他会在\(u\)往下走,但是这个时候,如果说\(dp[u][0]=dp[v][0]+w\),即从\(u\)往下走的最大距离会经过\(v\),这就矛盾了,所以我们需要一个从\(u\)往下走的次大值
基本实现思路:
先通过\(dfs1\)自下而上求出\(dp[u][0]\)和\(dp[u][1]\)
而后通过\(dfs2\)自上而下求出\(dp[u][2]\)
最后树的中心就是\(\sum{min(max(dp[u][0],dp[u][2])})\)
int n;
int dp[N][3]; // dp[u][0]:子树中的最大值,dp[u][1]:子树中的次大值,dp[u][2]:子树外的最大距离
vector<pii> g[N];
void dfs1(int u, int par)
{
for (auto &[v, w] : g[u])
{
if (v == par)
continue;
dfs1(v, u);
if (dp[u][0] <= dp[v][0] + w)
{
dp[u][1] = dp[u][0];
dp[u][0] = dp[v][0] + w;
}
else if (dp[u][1] <= dp[v][0] + w)
dp[u][1] = dp[v][0] + w;
}
}
void dfs2(int u, int par)
{
for (auto &[v, w] : g[u])
{
if (v == par)
continue;
if (dp[u][0] == dp[v][0] + w)
dp[v][2] = max(dp[u][2] + w, dp[u][1] + w);
else
dp[v][2] = max(dp[u][2] + w, dp[u][0] + w);
dfs2(v, u);
}
}
Great Cow Gathering G
题解
考虑换根\(DP\)求出每个点作为集会地点的路程和
我们定义\(dp1_u\)为以\(u\)为根的子树中的节点到\(u\)的路程和,\(sum_u\)为以\(u\)为根的子树中的点权和
我们可以通过第一次\(dfs\)得到:
\[sum_u = \sum sum_v + a_u\ (u本身的点权)\\ dp1_u = \sum (dp1_v + sum_v * d_{u, v}) \]我们定义\(dp2_u\)代表\(u\)的父亲作为\(u\)的子树时产生的贡献,\(ans_u\)代表\(u\)作为集会地点时的路程和
显然\(ans_u = dp1_u + dp2_u\)
我们通过第二次\(dfs\)得到:
\[dp2_v = ans_u - (dp1_v + sum_v * d_{u,v}) + (S - sum_v) * d_{u, v}\\ S = \sum a_i \]时间复杂度:\(O(n)\)
const int N = 1e5 + 10, M = 4e5 + 10;
int n, S;
int sum[N], a[N], dp1[N], dp2[N], ans[N];
vector<pair<int, int>> g[N];
void dfs1(int u, int par)
{
sum[u] = a[u];
for (auto [v, w] : g[u])
{
if (v == par)
continue;
dfs1(v, u);
dp1[u] += dp1[v] + sum[v] * w;
sum[u] += sum[v];
}
}
void dfs2(int u, int par)
{
ans[u] = dp1[u] + dp2[u];
for (auto [v, w] : g[u])
{
if (v == par)
continue;
dp2[v] = ans[u] - (dp1[v] + sum[v] * w) + (S - sum[v]) * w;
dfs2(v, u);
}
}
void solve()
{
cin >> n;
for (int i = 1; i <= n; ++i)
{
cin >> a[i];
S += a[i];
}
for (int i = 1; i < n; ++i)
{
int u, v, w;
cin >> u >> v >> w;
g[u].push_back({v, w});
g[v].push_back({u, w});
}
dfs1(1, 0);
dfs2(1, 0);
int mi = INF;
for (int i = 1; i <= n; ++i)
mi = min(mi, ans[i]);
cout << mi << endl;
}
Maximum White Subtree
题解
显然对于一个节点\(u\)来说,要使得包含\(u\)的连通子图中\(cnt_1 - cnt_2\)最大化,那么\(u\)显然可以是根节点
所以考虑如果每个节点作为根节点时对答案的贡献,考虑换根\(DP\)
我们定义\(dp1_u\)为以\(u\)为根的子树中白点和黑点个数差的最大值
我们可以通过第一次\(dfs\)求出\(dp1_u\)
\[dp1_u = \sum dp1_v,\ dp1_v > 0,v是u的儿子 \]我们定义\(dp2_u\)为\(u\)的父亲作为\(u\)子树时对答案产生的贡献,\(ana_u\)为\(u\)作为整棵树的根节点时,白点和黑点个数差的最大值
显然\(ans_u = dp1_u + dp2_u\),\(dp2_1 = 0\)
我们可以通过第二次\(dfs\)求出\(dp2_u\)
\[\begin{equation} \left\{ \begin{array}{lr} dp2_v = max(0, ans_u),\ \ dp1_v \leq0\\ dp2_v = max(0, ans_u - dp1_v),\ \ dp1_v > 0 \end{array} \right. \end{equation} \]时间复杂度:\(O(n)\)
// https : // codeforces.com/problemset/problem/1324/F
const int N = 2e5 + 10, M = 4e5 + 10;
int n;
int a[N], dp1[N], dp2[N], ans[N];
vector<int> g[N];
void dfs1(int u, int par)
{
if (a[u] == 1)
dp1[u] = 1;
else
dp1[u] = -1;
for (auto v : g[u])
{
if (v == par)
continue;
dfs1(v, u);
if (a[v] == 1)
dp1[u] += dp1[v];
else if (dp1[v] > 0)
dp1[u] += dp1[v];
}
}
void dfs2(int u, int par)
{
ans[u] = dp1[u] + dp2[u];
for (auto v : g[u])
{
if (v == par)
continue;
if (a[v] == 1 || dp1[v] > 0)
{
if (ans[u] - dp1[v] > 0)
dp2[v] = ans[u] - dp1[v];
else
dp2[v] = 0;
}
else
{
if (ans[u] > 0)
dp2[v] = ans[u];
else
dp2[v] = 0;
}
dfs2(v, u);
}
}
void solve()
{
cin >> n;
for (int i = 1; i <= n; ++i)
cin >> a[i];
for (int i = 1; i < n; ++i)
{
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
dfs1(1, 0);
dfs2(1, 0);
for (int i = 1; i <= n; ++i)
cout << ans[i] << "\n "[i < n];
}