UOJ Logo Wolverine的博客

博客

标签
暂无

[20联赛集训day8]蛋爆爆 题解

2020-10-21 19:53:36 By Wolverine

题目大意

给出一个 $n$ 个节点的树,初始时你在节点 $1$,你需要将每一个节点至少遍历一次(可以重复经过),求最小所需时间。
对于给出的树上边,经过这些边花费一个单位时间
除此之外,你可以从任意节点不消耗任何时间直接传送到 $1$ 号节点,并且该操作可以使用无限次。
求将该树按要求遍历至少需要多少单位时间

解题思路

这题据说是一道很套路的树形DP,虽然我没见过

设 $f_{x,0}$ 表示将 $x$ 的子树遍历并且回到 $x$ 所需要的最少时间
$f_{x,1}$ 表示将 $x$ 的子树遍历并且不回到 $x$ 所需要的最少时间

那么转移应该很显然:
PS:$d$ 表示深度,根节点的深度为 $1$
$f_{x,0}=\sum\limits_y \min(f_{y,0}+2,f_{y,1}+d_x)$
两种情况分别为从 $y$ 回到 $x$ 和 从根节点回到 $x$
$f_{x,1}=f_{x,0}-\max\limits_{y}\{\min(f_{y,0}+2,f_{y,1}+d_x)-(f_{y,1}+1)\}$
显然只能选择一个子节点的子树不回来,则选不回来省时间最多的那个

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int Maxn=1000000+10;
const int Maxm=Maxn<<1;
const int inf=0x3f3f3f3f;
int head[Maxn],f[Maxn][2];
int nxt[Maxm],to[Maxm],d[Maxn];
int n,edgecnt;
inline int read()
{
    int s=0,w=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
    while(ch>='0' && ch<='9')s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
    return s*w;
}
inline void add(int x,int y)
{
    ++edgecnt;
    nxt[edgecnt]=head[x];
    to[edgecnt]=y;
    head[x]=edgecnt;
}
void dfs(int x,int fa)
{
    d[x]=d[fa]+1;
    int tmp=-inf;
    for(int i=head[x];i;i=nxt[i])
    {
        int y=to[i];
        if(y==fa)continue;
        dfs(y,x);
        f[x][0]+=min(f[y][0]+2,f[y][1]+d[x]);
        tmp=max(tmp,min(f[y][0]+2,f[y][1]+d[x])-(f[y][1]+1));
    }
    if(tmp==-inf)f[x][1]=0;
    else f[x][1]=f[x][0]-tmp;
}
int main()
{
//    freopen("in.txt","r",stdin);
    n=read();
    for(int i=2;i<=n;++i)
    {
        int x=read();
        add(x,i),add(i,x);
    }
    dfs(1,0);
    printf("%d\n",min(f[1][1],f[1][0]));
    return 0;
}
共 1 篇博客