一棵大小为 n n n的无根树,给定某些边的颜色和要求的颜色(有的没有要求),你每次可以选择一条路径,将该路径上所有的边颜色取反,求最少操作次数以及此时的最小路径总长度
数据范围: n ≤ 1 0 5 n\leq 10^5 n≤105
树形 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的初始状态记得调成正无穷
#include
#include
#include
#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);
}