· · 算法·理论

今天我们来学习

如果一张图中只存在直接的父子关系,会构成一张特殊的图,而这张特殊的图就被我们称为

树的特征

而我们通常用边表示父子关系。

根结点

在一棵树中,有一个特定的结点称之为根,根本身是没有父亲结点的。

因此树可以分为两类

树的度

树跟图一样,也有度。
树的度:父亲结点的直接儿子数。

当指定根结点后,除了根结点,别的结点都只有一个父亲。一个结点可能有若干个子结点,没有子结点的结点称之为叶子结点。树上的所有结点一定连通,且没有环

连通:任意选取两个结点都可以从一个结点到另一个结点。

建树的方式

父子表示法:

用数组记录每个结点的父亲。fa_i记录i的父亲

孩子表示法

运用动态数组vector记录每个节点的子节点,和邻接表类似。

树的深度和子树

深度

深度表示从根结点到叶子结点经历的层数就是树的深度,依题目要求,根结点深度不固定。

子树

选定树中任意一个结点作为根,以这个点为根的树即是这棵树的子树。

讲子树就要现将树的大小了。

求深度和子树的通用代码(树的模版)

void dfs(int u,int f){
    dep[u]=dep[f]+1;//dep数组记录每个结点的深度
    siz[u]=1;//siz数组记录每个结点的子树大小
    for(int i=0;i<g[u].size();i++){
        int v=G[u][i];
        if(v!=f){//不重复走
            dfs(v,u);
            siz[u]+=siz[v];//让儿子把儿子的儿子和儿子的儿子的儿子...报上来
        }
    }
}

列题

学完上面的内容了,相信你对数树已经有了一定的认知,所以来试试下面这道题把题。

是不是很简单内,看完上面,我们知道了根结点是没有父亲的。因此我们只需要找到即在输入中出现过,又没有父亲的结点。儿子最多的直接去vector里面找就行了,最后排序输出,恭喜你,有{\color{#22AB22}AC{}}了一道题。

代码

#include<bits/stdc++.h>
#include<vector>
#include<queue>
using namespace std;
vector<int> g[1005];
bool vis[1005];
bool t[1005];
int main(){
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        int x,y;
        cin>>x>>y;
        g[x].push_back(y); 
        vis[y]=1;
        t[x]=1;
        t[y]=1;
    }
    int gen=0,maxn=0,x=0;
    for(int i=1;i<=1000;i++){
        if(!vis[i] && t[i]){
            gen=i;
        }
        if(g[i].size()>maxn){
            x=i;
            maxn=g[i].size();
        }
    }
    cout<<gen<<endl;
    cout<<x<<endl;
    sort(g[x].begin(),g[x].end());
    for(int i=0;i<g[x].size();i++){
        cout<<g[x][i]<<' ';
    }
    return 0;
}

恭喜你,已经掌握树的度了,接下了是树的深度。

树的深度

这道题其实就是一棵树,你的深度越小,你在公司里的地位越高。地位最高的就是没有上司的了。由于没有上司的人不止一位,所以这道题中的树也不止一棵,所以不能一次搜索就完工,每次遇到-1就进行一次深搜计算一次深度。

代码

#include<bits/stdc++.h>
#include<vector>
using namespace std;
int dep[1000005];
vector<int> g[1000005];
int z[1000005];
int maxn;
void dfs(int u,int f){
    dep[u]=dep[f]+1;
    for(int i=0;i<g[u].size();i++){
        int v=g[u][i];
        if(v!=f){
            dfs(v,u);
        }
    }
    return;
}
int main(){
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>z[i];
        if(z[i]!=-1){
            g[z[i]].push_back(i);
            g[i].push_back(z[i]);  
        }
    }
    for(int i=1;i<=n;i++){
        if(z[i]==-1){
            dfs(i,0);
        }
    }
    for(int i=1;i<=n;i++){
        maxn=max(maxn,dep[i]);
    }
    cout<<maxn;
    return 0;
}

好了万事俱备只欠"子树"。

这时候就会有人问了:这道题的根结点都不确定,那我们要怎么计算子树个数呢?是不是还得正着求一次子树,在反着求一次子树呢?其实这都不算事,等最后在告诉你们(#^.^#)

此时我们先按1为根结点,求出所有点的子树个数。

输出答案时,会得到两个点的子树,此时会有两种情况。

此时不管是哪种情况,都满足两端的国家,一端是子树个数小的,而另一端就是除了子树个数小的以外的结点(n-子树个数小的)。

代码

#include<bits/stdc++.h>
#include<vector>
using namespace std;
int n;
long long l[1000005],r[1000005],c[1000005];
vector<int> g[1000005];
int siz[1000005];
void dfs(int u,int f){
    siz[u]=1;
    for(int i=0;i<g[u].size();i++){
        int v=g[u][i];
        if(v==f)continue;
        dfs(v,u);
        siz[u]+=siz[v];
    }
    return;
}
int main(){
    cin>>n;
    for(int i=1;i<n;i++){
        cin>>l[i]>>r[i]>>c[i];
        g[l[i]].push_back(r[i]);
        g[r[i]].push_back(l[i]);  
    }
    dfs(1,0);//以1作为根结点的子树
    long long sum=0;
    for(int i=1;i<n;i++){
        int minn=min(siz[l[i]],siz[r[i]]);//获取子树小的作为一边
        int k=n-minn;//获得另一边
        sum+=abs(k-minn)*c[i];
    }
    cout<<sum;
    return 0;
}