【笔记+模板】图论中的树

    • 名词解释
    • 树的性质
    • 树的遍历
    • 树上lca
      • 倍增法
    • 树上前缀和
    • 树的重心
      • 定义
      • 求解流程
      • 例题
    • 树的直径
      • 两边dfsbfs
      • 小哥哥教的树形DP
    • 一些不会的东西

【笔记+模板】图论中的树_第1张图片

名词解释

1.树是一种无向连通无环图;是基本数据结构的一种;
通常我们会把树转为有根树来操作;
2.节点的度:一个节点含有的子树的个数称为该节点的度;A节点的度为3
3.叶节点或终端节点:度为0的节点称为叶节点;
4.树的度:一棵树中,最大的节点的度称为树的度;
5.节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
6.树的高度或深度:树中节点的最大层次;
7.森林:由m(m>=0)棵互不相交的树的集合称为森林;
8.二叉树:每个节点最多含有两个子树的树称为二叉树;
9.满二叉树:如果一棵二叉树的结点要么是叶子结点,要么它有两个孩子结点,这样的树就是满二叉树。
10.完全二叉树:完全二叉树:叶节点只能出现在最下层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树

树的性质

树:有n个节点,n-1条边,任意两点之间有且仅有一条路径;
二叉树因为其定义有独特的性质;

树的遍历

通过dfs实现;
二叉树遍历:
先序遍历:根左右
中序遍历:左根右
后序遍历:左右根

树上lca

lca:树上两点的最近公共祖先;
用途:树上任意两点的路径都是唯一的。并且这条路径一定经过lca,遇到和路径有关的问题(比如查询x到y路径的长度,查询x到y路径的节点权值最小值等等)就可以拆分成两部分:x到lca和y到lca

倍增法

维护:sum,min,max等(满足区间加法);
代码示例:倍增lca运用了试探法,预处理nlogn;单组查询logn;

//预处理
void dfs(int f,int t){
    deep[t]=deep[f]+1;//深度 
    fa[t][0]=f;
    for(int i=1;fa[t][i-1];i++){
        fa[t][i]=fa[fa[t][i-1]][i-1];
    }
    for(x是t的子节点){
        dis[x]=dis[t]+v;//到根节点的距离 
        dfs(t,x);
    } 
}
//找x,y的lca
int lca(int x,int y){
    if(deep[y]>deep[x]) swap(x,y);
    int dd=deep[x]-deep[y];
    for(int i=0;i<=20;i++){//跳到同一深度
        if(dd&(1<if(x==y) return x;
    for(int i=20;i>=0;i--){
        if(fa[x][i]==fa[y][i]) continue;//重合不跳
        x=fa[x][i];y=fa[y][i];//不重合,跳
    }
    return fa[x][0];//最后跳到lca的儿子;
} 

树上前缀和

根路径前缀和sum2[i],指i到根节点所有节点的权值之和。
路径节点权值和:假如要求x到y路径的权值和,x,y的lca是z。则可以用sum[x]+sum[y]-2sum[z]+value[z]
子树前缀和sum1[i],指i的子树(包括i本身)所有节点的权值之和。
路径修改:
设定一个修改数组change。如果要对x到y路径上的所有点权值+k,lca为z。那么change[x]+=k,change[y]+=k,change[z]-=k,change[fa[z]]-=k。这样如果最后对change[i]求前缀和的话,最后得到的结果就是i权值的修改量,
特点:O(1)修改,O(n)查询;
例题:运输计划;

树的重心

定义

树的重心也叫树的质心。找到一个点,其所有的子树中最大的子树节点数最少,那么这个点就是这棵树的重心,删去重心后,生成的多棵树尽可能平衡。

求解流程

无根树转有根树
定义数组
d[i]表示以i为根的子树的节点总数(不包括i号节点)
ans[i]表示以i为根的子树中节点总数最大的子树的节点数

数组更新
u的子节点v
< u,v >
d[u]+=d[v]+1;
ans[u]=max(ans[u],d[v]+1)

对于ans数组,由于在u节点上方,还有一颗子树,故
ans[u]=max(ans[u],n-1-d[u])

例题

https://vjudge.net/problem/SGU-134
给你一个连通的无向图,他有N 个顶点和 N-1 条边 (一棵树)。现在你需要找到这棵树的重心。现在定义树的重心,树的每一个顶点有一个权值。考虑顶点k。如果从图中删除k号顶点(连带的边也一起被删除),剩下的图将只有 N-1 个顶点而且可能由多个连通分量组成。显然每一个连通分量还是一棵树。那么k号顶点的权值就是删除它以后剩下的连通分量中顶点数最多的顶点个数,删除k之后。这 N 个顶点中权值最小的就是重心。
输入

第一行是整数 N (1<=N<=16 000)。接下来 N-1 行每行两个整数, a 和 b,用空格隔开,表示顶点 a 和 顶点 b之间有一条边。
输出

第一行输出两个整数,最小的权值和重心的个数,第二行按递增顺序输出这些重心的编号。数据之间用一个空格隔开。

样例输入

71 22 32 41 55 66 7
样例输出

3 1
1

AC代码
#include
#include
#include
using namespace std;
const int N=16000+500;
int to[N<<1],nxt[N<<1],fst[N];
int ans[N],d[N],out[N];
int tot,cnt,n,m,a,b,minn=(int)1e9+7;
void build(int x,int y){
    to[++tot]=y;
    nxt[tot]=fst[x];
    fst[x]=tot;
}
void dfs(int x,int fa){
    d[x]=0;
    for(int i=fst[x];i!=-1;i=nxt[i]){
        int v=to[i];
        if(v==fa) continue;
        dfs(v,x);
        d[x]+=(d[v]+1);
        ans[x]=max(ans[x],d[v]+1);
    }
    ans[x]=max(ans[x],n-1-d[x]);
}
int main(){
    memset(fst,-1,sizeof(fst));
    scanf("%d",&n);
    for(int i=1;iscanf("%d%d",&a,&b);
        build(a,b),build(b,a);
    }
    dfs(1,0);

    for(int i=1;i<=n;i++){
        //printf("i=%d,d=%d,ans=%d\n",i,d[i],ans[i]);
        minn=min(ans[i],minn);
    }
    printf("%d ",minn);
    for(int i=1;i<=n;i++){
        if(ans[i]==minn) out[++cnt]=i;
    }
    printf("%d\n",cnt);
    for(int i=1;i<=cnt;i++) printf("%d ",out[i]);
    return 0;
}

树的直径

没有找到合适的例题,所以先把以前的笔记丢上来

两边dfs、bfs

http://www.cnblogs.com/wuyiqi/archive/2012/04/08/2437424.html

小哥哥教的树形DP

1.无权图
将无根树转为有根树
x的子节点为s1,s2,s3。。。
设d[i]为以i为根节点的子树向下的最长链
ans[i]为以i为根节点的子树,经过i点(以i点为折点)的最长链

d[x]=1+d[sk] sk取遍x的子节点。
ans[x]=2+max d[si],d[sj],最长和次长的链
特判x只有一个子节点 ans[x]=1+d[si]

先知道链是什么,发现链总会折一下,枚举折点,除去折点后,就是两条链啦
2.有权图
两边dfs,把边权拆成都是一,塞点,就和无权图相同。
发现拆边塞点是不必要的,直接dfs即可
上一题:无权图
设d[i]为以i为根节点的子树向下的最长链
d[x]=1+max d[y]
ans[i]为以i为根节点的子树,经过i点(以i点为折点)的最长链
ans[x]=2+d[si],d[sj], 最长和次长的链
ans[x]=1+d[si],只有一个节点
上题中,1,2为边权,将边权换掉
d意义不变
d[x]=max(d[y]+e[x][y])
ans=d[y1]+e[x][y1]+d[y2]+e[x][y2],两两配对,找e+d最大和次大值。
只有一个子节点 ans[x]=1+d[y]+e[x][y]

3.有负权

可能有负权,两边dfs不能做,
【笔记+模板】图论中的树_第2张图片
如果选择了左侧下方的点,最长链为3,选右侧,最长链为4

Dp仍然可行

#include
struct edge{
    int x,nxt,z;
}e[2*N];//无根树,双向边,
int hd[N];//first
int l;
void link(int f,int t,int v){
    e[++l]=(edge){t,hd[f],v};
    hd[f]=l;
}
void dfs1(int x,int fa){
    d[x]=0;//还是0,因为有负权的话,可以选择不走 
    for(int p=hd[x];p;p=e[p].nxt){
        int y=e[p].x;
        if(y==fa) continue;
        dfs1(y,x);
        d[x]=max(d[x],d[y]+e[p].z);
    }
}
void dfs2(int x,int fa){
    ans[x]=0;
    int max1=-inf,max2=-inf;//有负权啊 
    for(int p=hd[x];p;p=e[p].nxt){
        int y=e[p].x;
        if(y==fa) continue;
        dfs2(y,x);//随便位置,因为ans不依赖于下面的ans 
        int t=d[y]+e[p].z;
        if(t>max1){
            max2=max1;
            max1=t;
        }else if(t>max2)
                    max2=t;
    }
    if(max1==-inf)
        ans[x]=0;
        else if(max2=-inf) ans[x]=max1;
        else ans[x]=max1+max(0,max2); 

}
void dfs(int x,int fa)// 合并版
{
    d[x]=0;//还是0,因为有负权的话,可以选择不走 
    for(int p=hd[x];p;p=e[p].nxt){
        int y=e[p].x;
        if(y==fa) continue;
        dfs(y,x);
        d[x]=max(d[x],d[y]+e[p].z);
        //更新 d 
        int t=d[y]+e[p].z;
        if(t>max1){
            max2=max1;
            max1=t;
        }else if(t>max2)
                    max2=t;
        //求最长次长 
    }
    if(max1==-inf)
        ans[x]=0;
        else if(max2=-inf) ans[x]=max1;
        else ans[x]=max1+max(0,max2); //次大值也为负,可以不选。 
}
int main(){
    for(int i=1;iscanf("%d%d%d",&x,&y,&z);
        link(x,y,z);
        link(y,x,z); 
    }
    dfs1(1,0);
    dfs2(1,0); 
    int rans=0;

}

一些不会的东西

树的直径:树的最长链
求法: 两遍BFS :先任选一个起点BFS找到最长路的终点,再从终点进行BFS,则第二次BFS找到的最长路即为树的直径;

你可能感兴趣的:(笔记,===图论===)