树的最小距离和--题解 P1395 【会议】

· · 题解

这一题树上动态规划,是我首次自己搞出来的。

以为自己的不是正解,没想到跟正解截然无二

当然是定义f[i]=以i为根的最小距离和。

随便想想可以搞出, 一个状态是可以被任意一个相邻状态推出的。

设j与i相邻:

f[i]=f[j]+(n-size[i])-size[i]

,size[]指去除<i,j>这条边(也就是删去j点)后有i的这个联通块的大小(节点个数)。

这个方程的意思是:

把树分为包含i一块,暂且称为i块;包含j一块,称为j块。

j块上的点到j的距离,和i块上的点到j的距离,构成了f[j].

如果把j挪到i上,j块上的点(共n-size[i]个)都需要再跑一步;而i块上的点(size[i]个),都可以少跑一步。类似于线段上取点使得距离和最小经典问题的思想??

但是如果顺序杂乱无章,你就很不好记录size,也就无从转移。

根据树上动态规划的基本思路,我们想到钦定1为根,按照深度分块,size记录的也就是子树的大小了

此时又有一个问题:是根转叶还是叶转根呢?我们发现,就算我们知道方程,知道size,第一个f[]的值还是得暴力算出来的。

当然,求诸叶不如求一根,那么思路就基本确定了:

1.DFs1():求出size,以及f[1].

2.DFs2():求出f[]数组其他值。

根据树的常识,以一个节点为根的距离和便是所有点深度的和。

但是这里开一个dep[]似乎有点浪费,于是我们有两个选择:一,是记录dep,DFs1返回size(当然,是不行的,size以后还要用),二,

就是记录size,DFs1返回dep。

于是O(2n)的优秀复杂度通过本题。

#include<cstdio>
#include<iostream>
#include<cstring>
#define maxn 50001

int n;
struct Edge{
    int x,y,nxt;
}e[maxn<<1];
int in[maxn],tot;
inline void ins(int x,int y){
    e[++tot].x=x;
    e[tot].y=y;
    e[tot].nxt=in[x];
    in[x]=tot;
}

int size[maxn];
int DFs1(int v,int fa,int dep){
    size[v]=1;
    int deps=dep;
    for(register int i=in[v];i;i=e[i].nxt){
        int u=e[i].y;
        if(u==fa)continue;
        deps+=DFs1(u,v,dep+1);
        size[v]+=size[u];
    }
    return deps;
}
int f[maxn]; 
void DFs2(int v,int fa){
    for(register int i=in[v];i;i=e[i].nxt){
        int u=e[i].y;
        if(u==fa)continue;
        f[u]=f[v]+(n-size[u])-size[u];
        DFs2(u,v);
    }
}

int main(){
    std::cin>>n;
    for(register int i=1,x,y;i<n;i++){
        std::scanf("%d %d",&x,&y);
        ins(x,y);
        ins(y,x);
    }
    f[1]=DFs1(1,0,0);
    DFs2(1,0);
    int ans=0x3f3f3f3f,ansi=1;
    for(register int i=1;i<=n;i++)
      if(ans>f[i]){
        ans=f[i];
        ansi=i;
      }
    std::printf("%d %d\n",ansi,ans);
    return 0;
}

比较下求n次dep和的O(n^2)算法,这种算法更好的利用了相邻点间的数量关系。