G2023选拔赛day2 T3 树形DP


我太弱了,这DP有没想出来,QWQ%%%jzm

一.题目

传送门

题目大意: 有一棵 n n n个节点的树,每条边长度为 1 1 1,颜色为黑或白。 可以执行若干次如下操作:选择一条简单路径,反转路径上所有边的颜色。
对于某些边,要求在操作结束时为某一种颜色。 给定每条边的初始颜色,求最小操作数,以及满足操作数最小时,最小的操作路径长度和。
1 ≤ n ≤ 1 0 5 1\leq n \leq 10^5 1n105

二.题解

首先得出一个结论,需要执行翻转的次数是每个需要翻转的子树中度数为基数的点的个数,比如我的需要翻转的路径末端的节点在这颗需要翻转的子树中度数为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;
}

Thanks!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值