玩个树

博客探讨了一种树的优化决策问题,其中最优决策下翻转路径两两不交。通过树动态规划(dp)解决,根据数据范围设计状态,并讨论了不同边权的转移策略,包括边权为0、1的情况。在转移过程中,注意合并子节点的状态,并处理单链成路的特殊情况。
摘要由CSDN通过智能技术生成

一、题目

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、解法

首先可以感知一个结论:最优决策下翻转路径两两不交(好想,好理解,好证)

一般都是树 d p dp dp了,可以根据数据范围设计 d p dp dp状态,设 d p [ i ] [ 0 / 1 ] dp[i][0/1] dp[i][0/1]为不留 / / /留一条链给父亲去处理,主要是转移,如果你不想讨论的话可以先枚举确定 d = 2 d=2 d=2的边,然后就好写很多,蒟蒻作者就用这个加链拿了 80 80 80分。

详细讲一下正解如何转移,先重新定义边权为 c ⊕ d c\oplus d cd,为 0 0 0不翻转, 1 1 1需要翻转, 2 , 3 2,3 2,3无所谓。如果边权 ≠ 0 \not=0 =0,那么就假定它需要翻转,用一个临时数组把 d p [ v ] [ 1 ] dp[v][1] dp[v][1]合并上来,这里有一个细节,在算 v v v的时候我们可以把 d p [ v ] [ 1 ] dp[v][1] dp[v][1]提前操作一步,如果其他的链合并上去了就 − 1 -1 1。如果边权 ≠ 1 \not=1 =1,那么直接把 d p [ v ] [ 0 ] dp[v][0] dp[v][0]并上来就行了。

最后还要考虑单链成路(单链就形成了操作路径, d p [ u ] [ 0 ] dp[u][0] dp[u][0] d p [ u ] [ 1 ] dp[u][1] dp[u][1]转移过来),考虑连向父亲边的需要翻转( d p [ v ] [ 1 ] dp[v][1] dp[v][1] d p [ v ] [ 0 ] dp[v][0] dp[v][0]转移,步数 + 1 +1 +1),初始化 d p [ u ] [ 1 ] = i n f dp[u][1]=inf dp[u][1]=inf(根据 d p dp dp的意义就容易推知了),方便写用 p a i r \tt pair pair

#include <cstdio>
#include <iostream>
using namespace std;
const int M = 100005;
const int inf = 0x3f3f3f3f;
#define pii pair<int,int>
#define make make_pair
int read()
{
    int num=0,flag=1;
    char c;
    while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;
    while(c>='0'&&c<='9')num=(num<<3)+(num<<1)+(c^48),c=getchar();
    return num*flag;
}
int n,tot,f[M];pii dp[M][2],tmp[2];
struct edge
{
	int v,c,next;
}e[2*M];
void dfs(int u,int p)
{
	dp[u][1]=make(inf,inf);
	for(int i=f[u];i;i=e[i].next)
	{
		int v=e[i].v;
		if(v==p) continue;
		dfs(v,u);
		tmp[0]=tmp[1]=make(inf,inf);
		if(e[i].c!=0)
		{
			for(int j=0;j<2;j++)
				tmp[j^1]=min(tmp[j^1],make(dp[u][j].first+dp[v][1].first-j,dp[u][j].second+dp[v][1].second+1));
		}
		if(e[i].c!=1)
		{
			for(int j=0;j<2;j++)
				tmp[j]=min(tmp[j],make(dp[u][j].first+dp[v][0].first,dp[u][j].second+dp[v][0].second));
		}
		dp[u][0]=tmp[0];dp[u][1]=tmp[1];
	}
	dp[u][1]=min(dp[u][1],make_pair(dp[u][0].first+1,dp[u][0].second));//考虑返父边的转移
	dp[u][0]=min(dp[u][0],dp[u][1]);//单链成路
}
signed main()
{
	n=read();
	for(int i=1;i<n;i++)
	{
		int u=read(),v=read(),c=read(),d=read();
		e[++tot]=edge{v,c^d,f[u]},f[u]=tot;
		e[++tot]=edge{u,c^d,f[v]},f[v]=tot;
	}
	dfs(1,0);
	printf("%d %d\n",dp[1][0].first,dp[1][0].second);
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值