ZOJ 3949 树形DP

题意

给一棵树加一条边,使得根到所有点的距离之和最小,问最小的距离之和是多少。

题解

一道很考思维的树形DP。首先的话,先用简单DFS求出每个点的子节点数量和以及总路径长度。然后再尝试加边,计算加边以后能减少的距离。最后总路径长度-减少最大的距离=所求长度。
计算减少距离利用的是DFS的思想,对于细节的考量还是比较详细的。首先的话就是对于中点的划分。中点选取dep/2+1作为中点,可以证明dep-(dep/2)+1<=dep/2+1(可以取到等号)。因此中点选择dep/2+1是刚好合适的。另外的话,我们可以计算出来每一层的点的个数。在计算能减少的距离的时候,首先要减去(这一层的点的个数-中点层点的个数),因为从中点以下(包括中点)到该连接点(不包括该点),距离都会因为连接点的变动而+1。连接点以下的点(包括连接点),距离都会-1。计算能减少的距离,与最大减少距离进行比较记录。并将这个距离传给下一层即可。

注意事项

有个细节需要注意一下。当初为了方便,我直接把记录减少距离的变量设置为了全局变量,而不是参数。后来才意识到,这样是绝对不可以的,因为这个距离变量是按照层进行传递的,而不是针对一个全局变量进行修改。

代码

#include <iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<cmath>
#include<queue>
#include<string>
#include<set>
#include<map>
#include<bitset>
#include<stack>
#include<string>
#define UP(i,l,h) for(int i=l;i<h;i++)
#define DOWN(i,h,l) for(int i=h-1;i>=l;i--)
#define W(a) while(a)
#define MEM(a,b) memset(a,b,sizeof(a))
#define INF 0x3f3f3f3f
#define LL long long
#define MAXN 200010
#define EPS 1e-10

using namespace std;

vector<int> vc[MAXN];
int num[MAXN],f[MAXN];
LL allans,total;

int dfs(int u,int pa,int deep){
    num[u]=1;
    int sz=vc[u].size();
    UP(i,0,sz){
        int x=vc[u][i];
//        cout<<x<<" "<<vis[x]<<endl;
        if(x!=pa){
            num[u]+=dfs(x,u,deep+1);
        }
    }
    total+=deep;
    return num[u];
}

void dfs2(int deep,int u,int pa,LL ans){
    if(deep){
        f[deep]=f[deep-1]+num[pa]-num[u];
//        cout<<"xxx"<<p<<" "<<num[p]<<" "<<num[u]<<endl;
    }
    int mid=deep/2+1;
    if(deep>1){
        ans-=(f[deep]-f[mid]);
        ans+=num[u];
    }
//    cout<<mid<<" "<<f[mid]<<endl;
    allans=max(ans,allans);
    int sz=vc[u].size();
    UP(i,0,sz){
        int x=vc[u][i];
        if(x!=pa)
            dfs2(deep+1,x,u,ans);
    }
}

int main(){
    int t;
    scanf("%d",&t);
    W(t--){
        MEM(vc,0);
        MEM(num,0);
        MEM(f,0);
        total=0;
        allans=0;
        int n;
        scanf("%d",&n);
        int a,b;
        UP(i,0,n-1){
            scanf("%d%d",&a,&b);
            vc[a].push_back(b);
            vc[b].push_back(a);
        }
        dfs(1,-1,0);
        dfs2(0,1,-1,0);
        printf("%lld\n",total-allans);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值