nssl 1469.W

D e s c r i p t i o n Description Description

一棵大小为 n n n的无根树,给定某些边的颜色和要求的颜色(有的没有要求),你每次可以选择一条路径,将该路径上所有的边颜色取反,求最少操作次数以及此时的最小路径总长度

数据范围: n ≤ 1 0 5 n\leq 10^5 n105


S o l u t i o n Solution Solution

树形 d p dp dp,这里强令1为根

F [ x ] [ 0 / 1 ] F[x][0/1] F[x][0/1]表示 x x x与其父亲相连的那条边是否被翻着时的两答案(即最小操作次数和此时的最小路径总长度,这两个东西是可以一起维护的)

如果我们要求 F [ x ] F[x] F[x],那必须要把儿子的答案计算过来,再累积上去

于是设 p ′ , q ′ p',q' p,q表示处理到 s o n son son之前的所有子树与 x x x的边翻转的总次数为偶数/奇数(与 F F F同类型,都存储两个答案)
p , q p,q p,q表示处理到 s o n son son时的上述值,考虑如何从 p ′ , q ′ p',q' p,q转移到 p , q p,q p,q

显然有方程

p = m i n { p ′ + F [ s o n ] [ 0 ] , q ′ + F [ s o n ] [ 1 ] ( 后 者 操 作 减 一 【 因 为 相 当 于 把 它 们 的 路 径 连 在 一 起 了 】 ) } p=min\{p'+F[son][0],q'+F[son][1](后者操作减一【因为相当于把它们的路径连在一起了】)\} p=min{p+F[son][0],q+F[son][1]()}
q = m i n { p ′ + F [ s o n ] [ 1 ] , q ′ + F [ s o n ] [ 0 ] } q=min\{p'+F[son][1],q'+F[son][0]\} q=min{p+F[son][1],q+F[son][0]}

注意,这里的+表示操作和长度都相加, m i n min min表示先比较操作,再比较长度,可以手打这个函数

这样,我们就得到了 x x x子树的答案,考虑和它的父亲连接起来

注意它和它父亲连接的边是有要求的,如果这条边是不是不能翻的的(即要求与颜色不相同或无要求,当然你也可以预处理)
F [ i ] [ 1 ] = m i n { p + 1 , q + 1 ( 仅 限 于 长 度 , 因 为 连 起 来 的 话 操 作 数 不 变 ) } F[i][1]=min\{p+1,q+1(仅限于长度,因为连起来的话操作数不变)\} F[i][1]=min{p+1,q+1()}
赋值 F [ i ] [ 1 ] F[i][1] F[i][1]复制为无穷大,表示不能过来

如果这条边不是必须翻的(即要求与颜色相同或无要求)
F [ i ] [ 0 ] = m i n { p , q } F[i][0]=min\{p,q\} F[i][0]=min{p,q}
否则 F [ i ] [ 0 ] F[i][0] F[i][0]赋值为无穷大

最终状态是 F [ 1 ] [ 0 ] F[1][0] F[1][0],因为 1 1 1是根,无父亲节点

T i p Tip Tip: q q q的初始状态记得调成正无穷


C o d e Code Code
#include<cstdio>
#include<cctype>
#include<stdlib.h>
#define LL long long
#define N 100010
using namespace std;int n,l[N],tot,a,b,c,d;
struct node{int next,to,fan;}e[N*2];
struct data{int cz,len;}F[N][2];
data operator +(data a,data b) 
{
	data c;
	c.cz=a.cz+b.cz;
	c.len=a.len+b.len;
	return c;
}
inline data min(data a,data b)
{
	if(a.cz<b.cz) return a;
	if(a.cz==b.cz&&a.len<b.len) return a;
	return b;
}
inline LL read()
{
	LL d=1,f=0;char c;
	while(c=getchar(),!isdigit(c)) if(c=='-') d=-1;f=(f<<3)+(f<<1)+c-48;
	while(c=getchar(),isdigit(c)) f=(f<<3)+(f<<1)+c-48;
	return d*f;
}
inline void add(int u,int v,int col,int req)
{
	int fan;
	if(col==req) fan=0;else
	if(req==2) fan=2;else fan=1;//判断这条边是否一定要翻
	e[++tot]=(node){l[u],v,fan};l[u]=tot;
	e[++tot]=(node){l[v],u,fan};l[v]=tot;
	return;
}
inline void dfs(int x,int fa,int lx)
{
	data p=(data){0,0},q=(data){1e9,1e9},p1=(data){0,0},q1=(data){1e9,1e9};//初始化
	for(register int i=l[x];i;i=e[i].next)
	{
		int y=e[i].to;
		if(y==fa) continue;
		dfs(y,x,i);
		p=min(p1+F[y][0],(data){q1.cz+F[y][1].cz-1,q1.len+F[y][1].len});
		q=min(p1+F[y][1],q1+F[y][0]);//状态转移
		p1=p;
		q1=q;//记得更新
	}
	if(e[lx].fan!=0) F[x][1]=min((data){p.cz+1,p.len+1},(data){q.cz,q.len+1});
	else F[x][1]=(data){1e9,1e9};
	if(e[lx].fan!=1) F[x][0]=min(p,q);
	else F[x][0]=(data){1e9,1e9};
	return;//更新
}
signed main()
{
	int size = 256 << 20; //250M
    char*p=(char*)malloc(size) + size;
    __asm__("movl %0, %%esp\n" :: "r"(p) );
	n=read();
	for(register int i=1;i<n;i++) a=read(),b=read(),c=read(),d=read(),add(a,b,c,d);
	dfs(1,-1,-1);
	printf("%d %d",F[1][0].cz,F[1][0].len);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值