最小距离 题解
Froranzen
·
·
个人记录
题目传送门~
大致题意
给你一棵有 n 个节点的树,有 n - 1 条长度为 x 的边将这 n 个点连接。
请找出一个点,使得当这个点为根节点时,所有点到它的距离之和最小,若有多个答案,请输出最小的节点,同时输出最小距离和。
同时我们规定:每一个点都会被 1 或 0 标记,若当前点 u 的标记为 1 , 则如果存在一个点 v 到 u 的距离为奇数,那么在统计 u 点为根节点时的距离之和的时候,v 点的贡献将视为 0。 同理,若 u 点的标记为 0 , 就是到 u 点距离为偶数的点的贡献将视为 0。
思路
1. 首先我们讲一下 O(n^2) 的做法
按照题意,我们对每一个点都进行一次求总距离和的操作。枚举点,时间复杂度为 O(n),以每个点为根节点遍历树,时间复杂度为 O(n), 总计 O(n^2)。
至于距离根节点 u 奇偶性的问题,我们只需要判断一下当前点 v 的深度即可:点 v 深度的奇偶性就决定了 v 节点到 u 距离的奇偶性。
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
inline char nc () {
static char buf[1 << 21], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread (buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++;
}
inline int read () {
register int x(0),f(1);
char ch = nc ();
while (ch > '9' || ch < '0') {if (ch == '-') f = ~f +1; ch = nc ();}
while (ch <= '9' && ch >= '0') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = nc ();}
return x * f;
}
int n;
const int MAXN = 100005;
struct node {
int to;
int next;
int val;
}e[MAXN << 1];
int head[MAXN << 1], cnt;
void add (int u, int v, int w) {
e[++cnt] = (node) {v, head[u], w};
head[u] = cnt;
}
int col[MAXN];
long long qwq[MAXN];
int dep[MAXN];
long long wqw = 0x3f3f3f3f;
void dfs_1 (int u, int fa) {
for (register int i(head[u]); i; i = e[i].next) {
int v = e[i].to;
if (v == fa) continue;
dep[v] = dep[u] + e[i].val;
dfs_1 (v, u);
}
}
void dfs_2 (int root, int u, int fa, int flag) {
for (register int i(head[u]); i; i = e[i].next) {
int v = e[i].to;
if (v == fa) continue;
if (((flag + e[i].val)% 2) != col[root]) qwq[root] += dep[v];
dfs_2 (root, v, u, flag + e[i].val);
}
}
int main () {
n = read ();
for (register int i(1); i <= n; ++i) col[i] = read ();
for (register int i(1); i < n; ++i) {
int u = read (), v = read (), w = read ();
add (v, u, w);
add (u, v, w);
}
for (register int i(1); i <= n; ++i) {
memset (dep, 0, sizeof (dep));
dfs_1 (i, 0);
dfs_2 (i, i, 0, 0);
if (qwq[i] < wqw) wqw = qwq[i];
}
for (register int i(1); i <= n; ++i) {
if (qwq[i] == wqw) {
printf("%d ", i);
break;
}
}
printf ("%lld", wqw);
}
2. 接下来就是重头戏了:O(n) 的换根dp做法
这种题目有一种通法:先求出以 1 为根节点时的距离之和。 之后再一边遍历树,一边利用父亲节点来更新子节点。这样就可以做到 O(n) 解决。
在这里,由于前面的代码和上文的暴力大同小异,我们直接讲树的遍历过程中,父亲节点是如何更新子节点的。
首先,分析父节点 u 和子节点 v 之间的距离,我们容易得出:
若 v 距 u 为偶数距离,则距 v 节点为偶数距离的点,距 u 节点必然为偶数距离,距 v 节点为奇数距离的点,距 u 节点必然为奇数距离。
若 v 距 u 为奇数距离,则距 v 节点为偶数距离的点,距 u 节点必然为奇数距离,距 v 节点为奇数距离的点,距 u 节点必然为偶数距离。
我们首先抛出一个定义 :
-
-
根据上文,在由父亲节点 u 转移到子节点 v 的方程可以先写一点:
如果 u 和 v 间距离为奇数
dp[v][0] = dp[u][1] + x
dp[v][1] = dp[u][0] + y
如果 u 和 v 间距离为偶数
dp[v][0] = dp[u][0] + x
dp[v][1] = dp[u][1] + y
此时,已经初具状态转移的雏形了。不急,我们再来慢慢分析一下 x 和 y 分别是什么。
在上图中,每两个点之间的距离为 1。
我们仔细观察 3 和 6 这两个节点。珂以得出以下结论:
根节点由 3 转移到 6 这个过程中:
1. 6 的子树内所有节点距根节点(也就是6)的距离比当 3 为根节点时全部减去了 3 和 6 之间的距离,也就是1。
2. 6 的子树外所有节点距根节点(也就是6)的距离比当 3 为根节点时全部加上了3 和 6之间的距离,也就是1。
我们再抛出一个定义 :
$size[i]$ 表示 i 的子树大小。
$w$ 是 $i$ 到 $j$ 的距离。
推广一下,珂以有下面的转移:
------------
$f[i]$ $=$ $f[j]$ $+$ (($n$ $-$ $size[i]$) $*w$)$−$ $size[i] * w
这为我们进一步推本题的状态转移提供了基础。
我们进一步猜想:上文的柿子是不考虑距离奇偶性来讲的,那么牵扯到距离的奇偶性后,是不是仍然一样的呢?
大胆猜想:
我们抛出一个定义:
-
-
$b$ 代表 $v$ 的子树外距 $v$ 为偶数距离的点的总和
$w$ 代表 $v$ 距 $u$ 的距离。
那么我们跟据上文的柿子珂以进一步推出:
------------
如果 $u$ 和 $v$ 间距离为奇数
$$dp[v][0] = dp[u][1] - size[v][1] * w+ a$$
$$dp[v][1] = dp[u][0] - size[v][0] * w+ b$$
如果 $u$ 和 $v$ 间距离为偶数
$$dp[v][0] = dp[u][0] - size[v][1] * w+ a$$
$$dp[v][1] = dp[u][1] - size[v][0] * w + b$$
------------
接下来,我们只需要推出 $a$ , $b$ 所代表的柿子就珂以切掉这道题了。
------------

------------
从上图中,我们珂以发现:
1. 一个节点 $u$ 若距 1 为偶数距离,则所有距 1 为偶数距离的节点,距 $u$ 也是偶数距离。
2. 一个节点 $u$ 若距 1 为偶数距离,则所有距 1 为奇数距离的节点,距 $u$ 也是奇数距离。
3. 一个节点 $u$ 若距 1 为奇数距离,则所有距 1 为偶数距离的节点,距 $u$ 也是奇数距离。
4. 一个节点 $u$ 若距 1 为奇数距离,则所有距 1 为奇数距离的节点,距 $u$ 也是偶数距离。
------------
珂能大家并不理解本蒟蒻为什么要说这些,现在本蒟蒻来解释一下吧。
根据简单的容斥原理,我们珂以知道:
**所有距 $u$ 为奇数(偶数)距离的点的总和 $-$ $u$ 的子树内距 $u$ 为奇数(偶数)的点的总和 $=$ $u$ 的子树外距 $u$ 为奇数(偶数)的点的总和**
而我们要求就的是 :
$v$ 的子树外,距 $v$ 为奇数距离的点的总和以及为偶数距离的点的总和。
------------
# 懂了吗?
------------
我们只需要在转移的时候判断一下 $v$ 的父亲 $u$ 距 1 的距离的奇偶性,状态转移就很好写了:
------------
```cpp
if (dep[u] % 2 == 1) {
if (e[i].val % 2 == 1) { //距 u 为奇数距离
f[v][1] = f[u][0] - (siz[v][0] * e[i].val) + ((siz[1][0] - siz[v][0]) * e[i].val);
f[v][0] = f[u][1] - (siz[v][1] * e[i].val) + ((siz[1][1] - siz[v][1]) * e[i].val);
}
else { //距 u 为偶数数距离
f[v][1] = f[u][1] - (siz[v][0] * e[i].val) + ((siz[1][1] - siz[v][0]) * e[i].val);
f[v][0] = f[u][0] - (siz[v][1] * e[i].val) + ((siz[1][0] - siz[v][1]) * e[i].val);
}
}
if (dep[u] % 2 == 0) {
if (e[i].val % 2 == 1) {
f[v][1] = f[u][0] - (siz[v][0] * e[i].val) + ((siz[1][1] - siz[v][0]) * e[i].val);
f[v][0] = f[u][1] - (siz[v][1] * e[i].val) + ((siz[1][0] - siz[v][1]) * e[i].val);
}
else { //距 u 为偶数数距离
f[v][1] = f[u][1] - (siz[v][0] * e[i].val) + ((siz[1][0] - siz[v][0]) * e[i].val);
f[v][0] = f[u][0] - (siz[v][1] * e[i].val) + ((siz[1][1] - siz[v][1]) * e[i].val);
}
```
------------
## 代码
```cpp
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
inline char nc () {
static char buf[1 << 21], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread (buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++;
}
inline int read () {
register int x(0),f(1);
char ch = nc ();
while (ch > '9' || ch < '0') {if (ch == '-') f = ~f +1; ch = nc ();}
while (ch <= '9' && ch >= '0') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = nc ();}
return x * f;
}
int n;
const int MAXN = 100005;
struct node {
int to;
int next;
int val;
}e[MAXN << 1];
int head[MAXN << 1], cnt;
void add (int u, int v, int w) {
e[++cnt] = (node) {v, head[u], w};
head[u] = cnt;
}
long long awa = 0x3f3f3f3f;
int dep[MAXN];
int siz[MAXN][2];
int col[MAXN];
long long f[MAXN][2];
void dfs_1 (int u, int fa) {
for (register int i(head[u]); i; i = e[i].next) {
int v = e[i].to;
if (v == fa) continue;
dep[v] = dep[u] + e[i].val;
dfs_1 (v, u);
}
if (dep[u] % 2 == 1) f[1][0] += dep[u];
else f[1][1] += dep[u];
}
void dfs_dep (int u, int fa) {
siz[u][0] += 1; //到本身距离为偶数距离。
for (register int i(head[u]); i;i = e[i].next) {
int v = e[i].to;
if (v == fa) continue;
dfs_dep (v, u);
if (e[i].val % 2 == 1) {
siz[u][1] += siz[v][0];
siz[u][0] += siz[v][1];
}
else if (e[i].val % 2 == 0){
siz[u][1] += siz[v][1];
siz[u][0] += siz[v][0];
}
}
}
void dfs_2 (int u, int fa) {
for (register int i(head[u]); i; i = e[i].next) {
int v = e[i].to;
if (v == fa) continue;
if (dep[u] % 2 == 1) {
if (e[i].val % 2 == 1) { //距 u 为奇数距离
f[v][1] = f[u][0] - (siz[v][0] * e[i].val) + ((siz[1][0] - siz[v][0]) * e[i].val);
f[v][0] = f[u][1] - (siz[v][1] * e[i].val) + ((siz[1][1] - siz[v][1]) * e[i].val);
}
else { //距 u 为偶数距离
f[v][1] = f[u][1] - (siz[v][0] * e[i].val) + ((siz[1][1] - siz[v][0]) * e[i].val);
f[v][0] = f[u][0] - (siz[v][1] * e[i].val) + ((siz[1][0] - siz[v][1]) * e[i].val);
}
}
if (dep[u] % 2 == 0) {
if (e[i].val % 2 == 1) {
f[v][1] = f[u][0] - (siz[v][0] * e[i].val) + ((siz[1][1] - siz[v][0]) * e[i].val);
f[v][0] = f[u][1] - (siz[v][1] * e[i].val) + ((siz[1][0] - siz[v][1]) * e[i].val);
}
else {
f[v][1] = f[u][1] - (siz[v][0] * e[i].val) + ((siz[1][0] - siz[v][0]) * e[i].val);
f[v][0] = f[u][0] - (siz[v][1] * e[i].val) + ((siz[1][1] - siz[v][1]) * e[i].val);
}
}
if (col[v] == 1) {
if (f[v][1] < awa) awa = f[v][1];
}
else {
if (f[v][0] < awa) awa = f[v][0];
}
dfs_2 (v, u);
}
}
int main () {
n = read ();
for (register int i(1); i <= n; ++i) col[i] = read ();
for (register int i(1); i < n; ++i) {
int u = read (), v = read (), w = read ();
add (u, v, w);
add (v, u, w);
}
dfs_1 (1, 0);
if (col[1]) awa = f[1][1];
else awa = f[1][0];
dfs_dep (1, 0);
dfs_2 (1, 0);
for (register int i(1); i <= n; ++i)
if (f[i][1] == awa || f[i][0] == awa) {printf ("%d ", i); break;}
printf ("%lld\n", awa);
return 0;
}
```
## 后言
qwq,这是本蒟蒻 OI 生涯中第一次出题,题的质量并不高,题解的讲解也是啰里啰唆的,同时还不规范。
但无论您对本蒟蒻出的这一道题目,乃至这整一场比赛的题目,是好评,还是差评(~~但好像全是差评~~),椋枨在这里都感谢您能来参加我们的比赛,愿意花费时间来做我们出的题目。
### 感谢大佬们的支持~