HDU 6867 Tree

题目

Problem Description
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.

Input
The first line contains one integer T (1≤T≤100000) — the number of test cases.

The first line of each test case contains only one integer n(1≤n≤5×105) — the number of vertices in the tree.

The second line of each test case contains n−1 integers p2,p3,…,pn(1≤pi<i) — the parent of each non-root node.

The sum of n over all test cases does not exceed 106.

Output
Print T integers — for each test case output the maximum number of pairs (x,y) that vertices x can move to y after adding one edge.

Sample Input
2
5
1 1 2 2
6
1 2 3 1 3

Sample Output
17
26

题意

有一颗以 1 为根的树,可以从父亲节点走到它的任意一个节点,设其中一个父亲节点是 fa,它的一个儿子节点是 son, 那么(fa, son) 即为一个配对(每个节点本身也算一个配对,即 (fa, fa)也是一个合法配对。并且,你可以在任意两个节点间增加一条边。
问:增加一条边后,最大的 配对 数量

方法

如果不增加一条边,只要计算每个点的 子节点数量 再 求和

// O(n)
vector<int> G[maxn]; //利用 vector 存子节点
int n; // 节点数量
int ans=n; // answer
void dfs(int i){ // dfs 求每个点的子节点数量
    for(int j=0;j<G[i].size();++j){
        int t=G[i][j]; dfs(t); ha[i]+=ha[t]+1;
    }
    ans+=ha[i]; //求出每个点的子节点数量后加入到答案里
}

增加一条边后,只要把增加的 最大配对数 加到原来的答案里面 即为最大值

  1. 显然 从叶子节点 连向 根节点 才能获得最大的配对数
  2. 可能有人和我一样,最开始以为只要 把深度最深的点和根节点 连起来 就会得到最大值
    那么 请模拟一遍 如下的图

在这里插入图片描述
所以说 深度最深的点 不一定是最优的

  1. 先来思考 我们要如何获得 新增加的 配对数
    从 根节点 连向 叶子节点的路径上的所有节点(除根节点) 都会产生新的配对, 每一个节点产生的新配对的数量 为 根节点的子节点数量 - 该节点的子节点数量,既然我们不知道把哪个叶子节点 和 根节点 连起来会得到 最大的配对数量,那我们就直接计算每一个 叶子节点 的 新的配对数量,然后取里面最大的一个就行了。
    听起来好像会很复杂, 其实不然。 因为计算每一个叶子节点的 新的配对数量 都是 一层层叠加的,那么我只要 dfs 一下就可以了(可能讲的不是很清楚,详见代码)
ll dpcnt; // 记录 可以获得的 最大的 新的配对数量, i 即当前的节点编号
void dph(ll sum, ll i){ // sum 记录 把 当前节点与根节点 相连 会获得的 新的配对数量
    sum+=ha[1]-ha[i];// 每一个节点产生的新配对的数量 为 根节点的子节点数量 - 该节点的子节点数量
    if(sum>dpcnt) dpcnt=sum; 
    if(G[i].empty()){// 如果是叶子节点 就 更新 dpcnt
        dpcnt=max(dpcnt, sum);
        return;
    }
    for(int j=0;j<G[i].size();++j) //递归它的子节点 直至叶子节点
        dph(sum, G[i][j]);
}

AC代码

// 应该可以算 O(n) 吧
#include <bits/stdc++.h>
using namespace std;
#define ll long long

const int maxn=5e5+5;

int T, n;
ll ans, dpcnt, ha[maxn]; // ha 数组保存每个节点的子节点数量(不包括自己)
vector<int> G[maxn];// 存 子节点

//快读部分
const int bsz=1<<18;
char bf[bsz],*head,*tail;
inline char gc(){
    if(head==tail){
        int l=fread(bf,1,bsz,stdin);
        tail=(head=bf)+l;
    }
    return *head++;
}
inline int read(){
    int x=0,f=1;
    char c=gc();
    for(;!isdigit(c);c=gc()) { if(c=='-') { f=-1;}}
    for(;isdigit(c);c=gc()) x=x*10+c-'0';
    return x*f;
}
//快读结束

void dfs(int i){// 求每一个节点的子节点数量
    for(int j=0;j<G[i].size();++j){
        int t=G[i][j]; dfs(t); ha[i]+=ha[t]+1;
    }
    ans+=ha[i];
}

void dph(ll sum, int i){//求最大的 新的匹配数量
    sum+=ha[1]-ha[i];
    if(G[i].empty()){// 如果是叶子节点 就 更新 dpcnt
        dpcnt=max(dpcnt, sum);
        return;
    }
    for(int j=0;j<G[i].size();++j) 
        dph(sum, G[i][j]);
}

int main(){
    T=read();
    while(T--){
        n=read(); ans=n, dpcnt=0;
        for(int i=1;i<=n;++i) G[i].clear(), ha[i]=0; // init
        for(int x, i=2;i<=n;++i) x=read(), G[x].push_back(i);
        dfs(1), dph(0, 1); 
        ans+=dpcnt;
        printf("%lld\n",ans);
    }
    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值