一、题目
二、解法
首先可以感知一个结论:最优决策下翻转路径两两不交(好想,好理解,好证)
一般都是树 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 c⊕d,为 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);
}