一.题目
题目大意: 有一棵 n n n个节点的树,每条边长度为 1 1 1,颜色为黑或白。 可以执行若干次如下操作:选择一条简单路径,反转路径上所有边的颜色。
对于某些边,要求在操作结束时为某一种颜色。 给定每条边的初始颜色,求最小操作数,以及满足操作数最小时,最小的操作路径长度和。
1 ≤ n ≤ 1 0 5 1\leq n \leq 10^5 1≤n≤105
二.题解
首先得出一个结论,需要执行翻转的次数是每个需要翻转的子树中度数为基数的点的个数,比如我的需要翻转的路径末端的节点在这颗需要翻转的子树中度数为1。
这一看是一道树形DP,考虑父亲节点与儿子节点的关系。
(都要用pair)定义
d
p
[
u
]
[
0
/
1
]
dp[u][0/1]
dp[u][0/1]表示节点
u
u
u到其父亲节点的边时候是否反转,first表示度数为基数的点的个数,second表示路径总长度,于是对于
u
u
u节点的每个儿子节点
v
v
v,我们用
t
m
p
1
tmp1
tmp1存有一条儿子到父亲的边需要翻转的最小代价,
t
m
p
0
tmp0
tmp0存没有儿子到父亲的边需要翻转的最小代价,
t
m
p
1
tmp1
tmp1初值为
i
n
f
inf
inf,于是对于每个儿子就有转移
t
m
p
1
=
m
i
n
(
d
p
[
v
]
[
1
]
+
t
m
p
0
,
d
p
[
v
]
[
0
]
+
t
m
p
1
)
tmp1=min(dp[v][1]+tmp0,dp[v][0]+tmp1)
tmp1=min(dp[v][1]+tmp0,dp[v][0]+tmp1)
表示 u u u到 v v v的边本来就需要翻转加上其他没有儿子到父亲的边需要翻转的最小代价 与 u u u到 v v v的边不需翻转加上其他儿子到父亲的边需要翻转的最小代价 求最小值
t m p 0 = m i n ( d p [ v ] [ 0 ] + t m p 0 , d p [ v ] [ 1 ] + t m p 1 ) tmp0=min(dp[v][0]+tmp0,dp[v][1]+tmp1) tmp0=min(dp[v][0]+tmp0,dp[v][1]+tmp1)
表示 u u u到 v v v的边不需翻转加上其他儿子到父亲的边也不需翻转 与 u u u到 v v v的边需要翻转就让其他儿子到父亲的边需要翻转的去配对而配成一条路径 求最小值
于是开始转移
d
p
[
u
]
[
0
/
1
]
dp[u][0/1]
dp[u][0/1]。
对于
u
u
u到其父亲节点的边需要翻转的
d
p
[
u
]
[
0
]
dp[u][0]
dp[u][0]附为最大值
d
p
[
u
]
[
1
]
=
m
i
n
(
m
a
k
e
(
t
m
p
1.
f
i
r
s
t
,
t
m
p
1.
s
e
c
o
n
d
+
1
)
,
dp[u][1]=min(make(tmp1.first,tmp1.second+1),
dp[u][1]=min(make(tmp1.first,tmp1.second+1),
m
a
k
e
(
t
m
p
0.
f
i
r
s
t
+
1
,
t
m
p
0.
s
e
c
o
n
d
+
1
)
)
make(tmp0.first+1,tmp0.second+1))
make(tmp0.first+1,tmp0.second+1))
对于 v v v到 u u u的边需要翻转的只需要增加路径长度,对于 v v v到 u u u的边不需要翻转的, u u u就是一个路径末端,它的度数为基数,所以 f i r s t first first和 s e c o n d second second都要加1
对于
u
u
u到其父亲节点的边不需要翻转的
d
p
[
u
]
[
1
]
dp[u][1]
dp[u][1]附为最大值
d
p
[
u
]
[
0
]
=
m
i
n
(
m
a
k
e
(
t
m
p
1.
f
i
r
s
t
+
1
,
t
m
p
1.
s
e
c
o
n
d
)
,
t
m
p
0
)
dp[u][0]=min(make(tmp1.first+1,tmp1.second),tmp0)
dp[u][0]=min(make(tmp1.first+1,tmp1.second),tmp0)
对于 v v v到 u u u的边需要翻转的, u u u就是路径末端,度数为1,但路径长度不增加,所以只需要 f i r s t first first加1,对于 v v v到 u u u的边不需要翻转的,不需要变化
(make=make_pair)
最后输出
d
p
[
1
]
[
0
]
.
f
i
r
s
t
/
2
,
d
p
[
1
]
[
1
]
.
s
e
c
o
n
d
dp[1][0].first/2,dp[1][1].second
dp[1][0].first/2,dp[1][1].second就行了
其实这道题目并不特别难想,主要是掌握第一个结论,并且牢记树形DP的套路,都是转移父亲节点与儿子节点,就行了。
三.Code
#include <cstdio>
#include <cstring>
#include <iostream>
#include <map>
#include <stack>
#include <queue>
#include <vector>
#include <cstdlib>
#include <algorithm>
using namespace std;
#define M 100005
#define INF 0x3f3f3f3f
int n, ans1, ans2;
vector <pair <int, int> > G[M];
pair <int, int> dp[M][2];
void Read (int &x){
x = 0; int f = 1; char c = getchar ();
while (c < '0' || c > '9') {if (c == '-') f = -1; c = getchar ();}
while (c >= '0' && c <= '9') {x = x * 10 + c - 48; c = getchar ();}
x *= f;
}
pair <int, int> operator + (pair <int, int> a, pair <int, int> b){
return make_pair (a.first + b.first, a.second + b.second);
}
pair <int, int> min (pair <int, int> a, pair <int, int> b){
if (a.first < b.first)
return a;
if (a.first == b.first && a.second < b.second)
return a;
return b;
}
void dfs (int u, int fa, int flag){
pair <int, int> tmp0, tmp1;
tmp0.first = tmp0.second = 0;
tmp1.first = tmp1.second = INF;
for (int i = 0; i < G[u].size(); i ++){
int v = G[u][i].first, k = G[u][i].second;
if (v != fa){
dfs (v, u, k);
pair <int, int> now0 = min (tmp1 + dp[v][1], tmp0 + dp[v][0]);
pair <int, int> now1 = min (tmp1 + dp[v][0], tmp0 + dp[v][1]);
tmp0 = now0;
tmp1 = now1;
}
}
if (flag == 0)
dp[u][1].first = dp[u][1].second = INF;
else
dp[u][1] = min (make_pair (tmp0.first + 1, tmp0.second + 1), make_pair (tmp1.first, tmp1.second + 1));
if (flag == 1)
dp[u][0].first = dp[u][0].second = INF;
else
dp[u][0] = min (tmp0, make_pair (tmp1.first + 1, tmp1.second));
}
int main (){
//freopen ("w.in", "r", stdin);
//freopen ("w.out", "w", stdout);
Read (n);
for (int i = 1; i < n; i ++){
int a, b, c, d;
Read (a), Read (b), Read (c), Read (d);
d = (d == 2) ? 2 : (c != d);
G[a].push_back (make_pair(b, d));
G[b].push_back (make_pair(a, d));
}
dfs (1, 0, 0);
printf ("%d %d\n", dp[1][0].first / 2, dp[1][0].second);
return 0;
}