D e s c r i p t i o n Description Description
一棵大小为 n n n的无根树,给定某些边的颜色和要求的颜色(有的没有要求),你每次可以选择一条路径,将该路径上所有的边颜色取反,求最少操作次数以及此时的最小路径总长度
数据范围: n ≤ 1 0 5 n\leq 10^5 n≤105
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);
}