hdu6567 Cotree (树形dp 树的重心)

Problem Description

Avin has two trees which are not connected. He asks you to add an edge between them to make them connected while minimizing the function
∑ni=1∑nj=i+1dis(i,j)
, where dis(i,j) represents the number of edges of the path from i to j. He is happy with only the function value.

Input

The first line contains a number n (2<=n<=100000). In each of the following n−2 lines, there are two numbers u and v, meaning that there is an edge between u and v. The input is guaranteed to contain exactly two trees.

Output

Just print the minimum function value.

Sample Input

3
1 2

Sample Output

4

思路:

树中所有点到某个点的距离和中,到重心的距离和是最小的;
如果有两个重心,那么他们的距离和一样

两次dfs树形dp找到两颗树的重心
把两个重心连接起来
再dfs树形dp一次计算答案

计算树上任意两点的距离和,转化为计算每条边被经过的次数乘上边权
每条边被经过的次数等于这条边分隔开
两端点分别所在的连通块的大小相乘
最后一次dfs树形dp的过程中
假设u是v的父节点,
以v为根的子树大小为sz[v]
则连接他们的边被经过的次数为(n-sz[v])*sz[v]

code:

#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define ll long long
const int inf=1e9;
const int maxm=1e5+5;
int head[maxm],nt[maxm<<1],to[maxm<<1],cnt;
int mark[maxm];
int sz[maxm],son[maxm];
int num;//第一个连通块的大小
int size;//第二个连通块的大小
int root1,root2;
int n;
ll ans;
void init(){
    memset(head,0,sizeof head);
    memset(mark,0,sizeof mark);
    cnt=1;
    num=0;
    ans=0;
}
void add(int x,int y){
    cnt++;nt[cnt]=head[x];head[x]=cnt;to[cnt]=y;
}
void dfs(int x){
    num++;//计算连通块大小
    sz[x]=1;
    son[x]=0;
    mark[x]=1;
    for(int i=head[x];i;i=nt[i]){
        int v=to[i];
        if(mark[v])continue;
        dfs(v);
        sz[x]+=sz[v];
        son[x]=max(son[x],sz[v]);
    }
}
void dfs2(int x){
    sz[x]=1;
    son[x]=0;
    mark[x]=1;
    for(int i=head[x];i;i=nt[i]){
        int v=to[i];
        if(mark[v])continue;
        dfs2(v);
        sz[x]+=sz[v];
        son[x]=max(son[x],sz[v]);
    }
    son[x]=max(son[x],size-sz[x]);
    if(son[x]<son[root2]){
        root2=x;
    }
}
void dd(int x,int fa){
    sz[x]=1;
    for(int i=head[x];i;i=nt[i]){
        int v=to[i];
        if(v==fa)continue;
        dd(v,x);
        sz[x]+=sz[v];
    	ans+=(ll)(n-sz[v])*sz[v];//计算答案
    }
}
signed main(){
    while(scanf("%d",&n)!=EOF){
        init();
        for(int i=1;i<=n-2;i++){
            int a,b;
            scanf("%d%d",&a,&b);
            add(a,b);
            add(b,a);
        }
        dfs(1);//dfs求第一个连通块的大小且初步计算每个点子树大小
        son[root1=0]=inf;//这个地方开始错写成sz[root=0]=inf,wa了几次才找出来
        for(int i=1;i<=n;i++){//找第一个连通块的重心
            if(mark[i]){//标记的在刚刚遍历的连通块里面
                son[i]=max(son[i],num-sz[i]);
                if(son[i]<son[root1]){
                    root1=i;
                }
            }
        }
        size=n-num;//另外一个连通块的大小
        for(int i=1;i<=n;i++){
            if(!mark[i]){//没被标记的在另外一个连通块
                son[root2=0]=inf;
                dfs2(i);//dfs找第二个连通块的重心
                break;
            }
        }
        add(root1,root2);//两个重心连接
        add(root2,root1);
        dd(1,0);//计算答案
        cout<<ans<<endl;
    }
    return 0;
}

你可能感兴趣的:(dp)