HDU 6867 Tree

Tree

You are given a tree consisting of n vertices numbered 1 to n rooted at node 1. The parent of the i-th vertices is pi. You can move from a vertex to any of its children. What’s more, you can add one directed edge between any two different vertices, and you can move through this edge too. You need to maximize the number of pairs (x,y) such that x can move to y through the edges after adding the edge. Note that x can also move to x.

题意
有一棵树,根是点1,现在让你添加一条有向边,求出(x_i,y_i)的对数。
其中(x_i,y_i)是一个点对,x_i可以和y_i相同。刚开始的图,点只能从父节点走到子节点,
若a是b的父节点,b是c的父节点,那么a可以走到c。

思路
容易知道,添加的一条有向边肯定是从叶子节点到根。
我们先考虑不添加这条边,那么满足的点对的数量,就是每个点的子树大小之和。
可以通过一遍dfs遍历这棵树求出来。

那么我们考虑怎么加这条边最优,想一下,从当前点加边到根节点1,这样会形成一个环。环上所有的点作为起点的话,每个起点能形成的点对都是n个。因为都能走到根节点,而根节点能走到所有其他点。那么说是环,其实是选择了一条链,然后把链的结尾接到了链头,链头显然是根节点。

接着进行第二次dfs,求出从根节点到当前节点的这条链上所有点的子树和(类似前缀和)
然后枚举每个点作为添加的有向边的起点,看哪个点的贡献最大即可,链的长度就是当前点的深度dep[i]
ma=max(ma,dep[i] * n-sum[i])
ma就是加了一条有向边后能增加的最大点对数
答案就是ma加上所有点的子树大小

当然也可以直接一次dfs 进行树型dp

#include 
using namespace std;
typedef long long ll;
struct node{
    int to,next;
}e[1<<21];
int tot,h[1<<21];
void add(int a,int b){
    e[tot]={b,h[a]};
    h[a]=tot++;
}
ll siz[1<<21],dep[1<<21],sum[1<<21];
void dfs1(int x,ll d){
    siz[x]=1;dep[x]=d;
    for(int i=h[x];~i;i=e[i].next){
        int to=e[i].to;
        dfs1(to,d+1);
        siz[x]+=siz[to];
    }
}
void dfs2(int x,ll v){
    sum[x]=siz[x]+v;
    for(int i=h[x];~i;i=e[i].next){
        dfs2(e[i].to,sum[x]);
    }

}
int main()
{
	ios::sync_with_stdio(0);
    int T;cin>>T;
    while(T--){
        int n;cin>>n;
        tot=0;
        for(int i=1;i<=n;i++) h[i]=-1;
        for(int i=2;i<=n;i++){
            int x;cin>>x;
            add(x,i);
        }
        dfs1(1,1);///计算每个点的子树大小
        dfs2(1,0);///统计从树根到当前点的子树大小和 类似前缀和
        ll ma=0,ans=0;
        for(int i=1;i<=n;i++){
			ans+=siz[i];///每个点的子树之和
            ma=max(ma,dep[i]*n-sum[i]);///求出加上一条有向边后 能增加的最大点对数
        }
        cout<<ans+ma<<endl;
    }
    return 0;
}

你可能感兴趣的:(dfs,思维,dp(动态规划))