[luogu4268][bzoj5195][USACO18FEB]Directory Traversal

时间:2023-03-09 02:48:34
[luogu4268][bzoj5195][USACO18FEB]Directory Traversal

题目大意

给你\(n\)个文件的关系,求出某一个点,这个点到叶节点的长度的总距离最短。(相对长度的定义在题目上有说明)

感想

吐槽一下出题人,为什么出的题目怎么难看懂,我看了整整半个小时,才看懂。

题解

首先数据给我们的是一棵树,一开始我审题不仔细以为是到每个节点的距离最短,就以为是要求树的重心。后来发现不对,而且节点之间的距离不怎么好处理,因为以不同的节点为起点,两节点之间的距离是会变化的。需要用树形dp来解决这个问题,首先预处理出以\(u\)为根节点的子树中有多少个叶节点,因为每一个相对路径的结尾都是没有\(/\)符号的,方便我们计算长度,还要预处理出每一个节点到根节点的长度,因为这个长度是一定的,算出来还是方便计算答案。还有一个是整棵树一共有多少个叶节点,因为在我们计算的节点的上方和下方的计算方法是不一样的。
那么我们树形dp定义状态为\(f[v]\)表示以\(v\)为根节点的最小相对距离之和。
核心的转移方程也是非常好理解的:\(f[v]=f[u]-(len[v]+1)*sz[v]+3*(sum-sz[v])\),解释一下:\(u\)为\(v\)的父亲,为什么是从父亲转移到儿子而不是从儿子转移到父亲?因为越靠近根节点的答案和根节点的答案越接近,每次更改只有一个文件的名字,所以我们先转移到儿子。我们考虑一下,每次我们从父亲节点转移到儿子节点,那么儿子节点以下所有叶子节点都会因为起始节点的改变而减少答案,这个减少的答案是\((len[v]+1)\times sz[v]\),什么意思?因为每次都有一个/号,还有一个\(v\)号字符串的长度,这些答案对于每一个叶子节点都会产生减少。同理在\(v\)节点的另外一侧,也就是\(v\)以上的子树,我们会因为其实节点的降低而增加一段的答案,也就是\(3\times(sum-sz[v])\),\(sum-sz[v]\)表示的是另一半子树中叶节点的个数,\(3\)是因为\(../\)的长度是\(3\),那么转移方程就得到了。

ac代码

# include <cstdio>
# include <cstring>
# include <algorithm>
# include <ctype.h>
# include <iostream>
# include <cmath>
# include <map>
# include <vector>
# include <queue>
# define LL long long
# define ms(a,b) memset(a,b,sizeof(a))
# define ri (register int)
# define inf (0x7f7f7f7f)
# define pb push_back
# define fi first
# define se second
# define pii pair<int,int>
# define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
using namespace std;
inline int gi(){
    int w=0,x=0;char ch=0;
    while(!isdigit(ch)) w|=ch=='-',ch=getchar();
    while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    return w?-x:x;
}
inline LL gll(){
    LL w=0,x=0;char ch=0;
    while(!isdigit(ch)) w|=ch=='-',ch=getchar();
    while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    return w?-x:x;
}
# define N 200005
struct edge{
    int to,nt;
}E[N<<1];
LL H[N],len[N],f[N],sz[N],dis[N];
int cnt,sum,n;
LL ans;
bool isleaf[N];
void addedge(int u,int v){
    E[++cnt]=(edge){v,(int)H[u]}; H[u]=cnt;
}
void dfs1(int u){
    for (int e=H[u];e;e=E[e].nt){
        int v=E[e].to;
        dis[v]=dis[u]+len[v]+1; //+1是因为每一次文件之间都有一个/
        dfs1(v); sz[u]+=sz[v];
    }
    if (isleaf[u]) sz[u]=1,dis[u]--,f[1]+=dis[u];//如果是叶节点那么就之间算就可以了
}
void dfs2(int u){
    for (int e=H[u];e;e=E[e].nt){
        int v=E[e].to;
        if (isleaf[v]) continue;
        f[v]=f[u]-(len[v]+1)*sz[v]+3*(sum-sz[v]);//转移方程
        ans=min(ans,f[v]);
        dfs2(v);
    }
}
int main(){
//  File("DirectoryTraversal");
    n=gi();
    for (int i=1;i<=n;i++){
        char s[20]; scanf("%s",s); len[i]=strlen(s);
        int m=gi();
        if (m==0) sum++,isleaf[i]=1;
        for (int j=1;j<=m;j++){
            LL id=gll();
            addedge(i,id);
        }
    }
    dfs1(1); ans=f[1]; dfs2(1);
    printf("%lld\n",ans);
    return 0;
}