欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

树上dp的入门知识

程序员文章站 2022-06-02 11:53:36
...

树的最大独立集

定义:对于一颗n个结点的无根树(没有确定根的树),选出尽量多的结点,使得任意二个结点均不相邻(称为树的最大独立集)。

思路:我们可以任意选一个点作为根结点,然后建树。

   用d(i)表示以i为根结点的子树的最大独立集大小。

对于结点i,只有两种选择,选或者不选,若选,则问题转化为求出i的孙子的d值之,若不选则转化为求i的儿子的d值之和。
状态转移方程:d(i) = max(1 + gs[i], s[i]);
代码:

#include<bits/stdc++.h>
using namespace std;
const int mx=1e5+10;
vector<int> v[maxn];
int d[maxn], s[maxn], gs[maxn],n;
int dfs(int u, int pre)
{
    for(int i = 0; i < v[u].size(); i++)
    {
        int m = v[u][i];
        if(m != pre)
            dfs(m, u);
        s[u] += d[m];
        if(pre != -1)
            gs[pre] += d[m];
    }
    d[u] = max(s[u], gs[u] + 1);
    return d[u];
}
void init()
{
    memset(d, 0, sizeof d);
    memset(s, 0, sizeof s);
    memset(gs, 0, sizeof gs);
    for(int i = 0; i < n; i++)
        v[i].clear();
}
int main()
{
    while(cin >> n)
    {
        init();
        for(int i = 0; i < n-1; i++)
        {
            int a, b;
            cin >> a >> b;;
            v[a].push_back(b);
            v[b].push_back(a);
        }
        cout << dfs(i, -1) << endl;
    }
    return 0;
}

树的重心

定义:树的重心,也称为质心。对于一棵n个结点的无根树,找到一个点(重心),使得把树以该点为根的有根树时,最大子树的结点数最小。换句话说,删除这个点后最大连通块的点(也叫结点)最小。

性质:
1.树中所有点到某个点的距离和中,到重心的距离和是最小的;如果有两个重心,那么他们的距离和一样。
2.把两个树通过一条边相连得到一个新的树,那么新的树的重心在连接原来两个树的重心的路径上。
3.把一个树添加或删除一个叶子,那么它的重心最多只移动一条边的距离。

树上dp的入门知识
如果以1为重心的话,相对于把1删去,分为二堆,{2},{3,4,5,6,7,8,9,10,11},最大子树的结点数为9.
所以我们很容易看出3是重心,{1,2},{4,7,8},{5,9,10},{6,11},最大子树的结点数为3.
还有一个知识点,就是上方子树,顾名思义就是3号结点上方的子树
然后我们怎么求重心呢,其实很简单。找到一个点,其所有的子树中最大的子树节点数最少,跑一遍dfs就好了。
具体看代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<cmath>
#include<cstring>
using namespace std;
typedef long long ll;
const int mx=1e5+10;
const double PI=cos(-1.0);
vector<int>G[mx];
int ans,sum,vis[mx],son[mx],n;
void dfs(int u,int pre)
{
    vis[u]=1;
    son[u]=0;
    int maxx=0;
    for(int i=0; i<G[u].size(); i++)
    {
        int v=G[u][i];
        if(v!=pre)
        {
            dfs(v,u);
            son[u]+=son[v]+1;//以u的子树的结点
            maxx=max(maxx,son[v]+1);//儿子,子树的结点
        }
    }
    maxx=max(maxx,n-son[u]-1);//上方子树的结点树,总结点n-(以u的子树的结点+1)
    if(maxx<ans||(ans==maxx&&sum>u))
    {
        ans=maxx;
        sum=u;
    }
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        sum=n;
        ans=n*n;
        for(int i=0; i<=n; i++)
            G[i].clear();
        for(int i=1,x,y; i<n; i++)
        {
            scanf("%d%d",&x,&y);
            G[x].push_back(y);
            G[y].push_back(x);
        }
        dfs(1,0);
        printf("%d %d\n",sum,ans);
    }
    return 0;
}

树的最长路径(最远点对)

定义:对于一课n个结点的无根树,找到一条最长路径。换句话说,要找到二个点,使得他们的距离最远。

其实就是找以i为根结点,找二个最长的子树。相加就好了

#include<bits/stdc++.h>
using namespace std;
const int N = 105;
vector<int>G[N];
int a[N], b[N];//a 存储到达最远结点的长度,b记录次长度
int ans = 0;
void dfs(int u, int pre)
{
    for(int i = 0; i < G[u].size() ;i++)
    {
        if(G[u][i]==pre)continue;
        dfs(G[u][i],u);
        if(a[G[u][i]]+1>=a[u])b[u]=a[u],a[u]=a[G[u][i]]+1;
        else if(a[G[u][i]]+1>b[u])b[u]=a[G[u][i]]+1;
    }

    ans = max(ans, a[u]+b[u]);
    return ;
}
int main()
{
    int n,u,v;
    scanf("%d",&n);
    for(int i=1; i<n; i++)
    {
        scanf("%d %d",&u,&v);
        G[u].push_back(v);
        G[v].push_back(u);
    }
    dfs(1,0);
    printf("%d\n",ans);
}