zoukankan      html  css  js  c++  java
  • 树形DP 学习笔记(树形DP、树的直径、树的重心)

    前言:寒假讲过树形DP,这次再复习一下。

    --------------

    基本的树形DP

    实现形式

    树形DP的主要实现形式是$dfs$。这是因为树的特殊结构决定的——只有确定了儿子,才能决定父亲。划分阶段的话一般是$f[i][j][0/1]$。$i$表示以$i$为根的子树,$j$一般表示保留$j$个子节点,$0/1$表示选/不选这个节点。一般第三维可以省去。

    基本的DP方程

    选择节点类

    $f[i][0]=f[j][1]$

    $f[i][1]=max/min(f[j][0],f[j][1])$

    背包类

    $f[v][k]=f[u][k]+val$

    $f[u][k]=max(f[u][k],f[v][k-1])$

    例题

    没有上司的舞会

    题目链接

    设$f[i][0/1]$表示$i$这个人去/不去舞会能获得的最大快乐指数。

    状态转移方程:

    $f[u][0]=max(f[v][0],f[v][1])$

    $f[u][1]=val[u]+f[v][0]$

    最大子树和

    题目链接

    设$f[i]$表示以$i$为根的子树所能获得的最大美丽指数。

    状态转移方程:$f[u]=val[u]+max(f[v],0)$

    注意有负数。

    树形DP求树的直径

    树的直径,即树上最远点对,一般有两种做法:两次$dfs$和树形$DP$。两种方法这里都会讲一下。

    两次$dfs$

    步骤:我们先找任意一点的最远点$p$,再找$p$的最远点$w$。从$p$到$w$的距离就是树的直径。

    证明:

    ①若$P$已经在直径上,根据树的直径的定义可知$Q$也在直径上且为直径的一个端点

    ②若$P$不在直径上,我们用反证法,假设此时$WQ$不是直径,$AB$是直径

    --->若$AB$与$PQ$有交点$C$,由于$P$到$Q$最远,那么$PC+CQ>PC+CA$,所以$CQ>CA$,易得$CQ+CB>CA+CB$,即$CQ+CB>AB$,与$AB$是直径矛盾,不成立。

    --->若$AB$与$PQ$没有交点,$M$为$AB$上任意一点,$N$为$PQ$上任意一点。首先还是$NP+NQ>NQ+MN+MB$,同时减掉$NQ$,得$NP>MN+MB$,易知$NP+MN>MB$,所以$NP+MN+MA>MB+MA$,即$NP+MN+MA>AB$,与$AB$是直径矛盾,所以这种情况也不成立。

    代码:

    //1
    #include<bits/stdc++.h>
    using namespace std;
    int dis[10005],ans,p;
    struct node
    {
        int next,to,dis;
    }edge[50005];
    int head[50005],cnt;
    inline void add(int from,int to,int dis)
    {
        edge[++cnt].next=head[from];
        edge[cnt].to=to;
        edge[cnt].dis=dis;
        head[from]=cnt;
    }
    void dfs(int now,int fa)
    {
        if (ans<dis[now])
        {
            ans=dis[now];p=now;
        }
        for (int i=head[now];i;i=edge[i].next)
        {
            int to=edge[i].to;
            if (to==fa) continue;
            dis[to]=dis[now]+edge[i].dis;
            dfs(to,now);
        }
    }
    void find(int x)
    {
        ans=0;
        dis[x]=0;
        dfs(x,0);
    }
    int main()
    {
        cin>>n>>m;
        for (int i=1;i<=m;i++)
        {
            int u,v,d;
            cin>>u>>v>>d;
            add(u,v,d);add(v,u,d);
        }
        find(1);
        find(p);
        cout<<ans;
        return 0;
    }

    树形DP

    设$dis1[i]$和$dis2[i]$表示以$i$为根的子树中以$i$为起点的最大距离和次大距离。

    状态转移方程:

    $if (dis1[i]<dis1[j]+edge[i].dis) dis2[i]=dis1[i],dis1[i]=dis1[j]+edge[i].dis$

    $else if (dis2[i]<dis1[j]+edge[i].dis) dis2[i]=dis1[j]+edge[i].dis$。

    理解:这样做就是,先看能否更新最大值,若能,它的次大值就是原先的最大值,再更新它的最大值;若不能,就看能不能更新次大值,若能,就更新,不能就不管它。

    $ans=max {dis1[i]+dis2[i]}$

    代码:

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    const int N=100005;
    int n,m,t,ans;
    int f1[N],f2[N];
    int first[N],v[N],w[N],next[N];
    void add(int x,int y,int z){    
        t++;
        next[t]=first[x];    
        first[x]=t;    
        v[t]=y;    
        w[t]=z;
    }
    void dp(int x,int father)
    {    
        int i,j;
        for(i=first[x];i;i=next[i])
        {
            j=v[i];        
            if(j==father) continue;
            dp(j,x);
            if(f1[x]<f1[j]+w[i])        
            {            
                f2[x]=f1[x];            
                f1[x]=f1[j]+w[i];        
            }        
            else if(f2[x]<f1[j]+w[i])          
                f2[x]=f1[j]+w[i];        
            ans=max(ans,f1[x]+f2[x]);    
        }
    }
    int main()
    {    
        int x,y,z,i;
        scanf("%d%d",&n,&m);    
        for(i=1;i<=m;++i)    
        {        
            scanf("%d%d%d",&x,&y,&z);        
            add(x,y,z);        
            add(y,x,z);    
        }    
        dp(1,0);    
        printf("%d",ans);    
        return 0;
    }

    树形DP求树的重心 

    对于一棵$n$个节点的无根树,找到一个点,使得把树变成以该点为根的有根树时,最大子树的节点数最小。换句话说,删除这个点后最大连通块的节点数最小,那么这个点就是树的重心。

    解法:任选一个节点为根,把无根树变成有根树,然后设$f[i]$表示以$i$为根的子树的节点个数。不难发现$f[i]=sum limits_{jin s[i]}f[j]+1$。程序实现很简单:只需要一次$dfs$,在无根树转化成有根树的同时计算即可。其实在删除节点$i$后,最大的连通块有多少个节点呢?节点$i$的子树中最大的有$max{f[j]}$个节点,$i$的上方子树中有$n-f[i]$个节点,在$DP$过程中根据定义就可以找出树的重心了。

    代码:

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <cstdlib>
    #include <cmath>
    #include <algorithm>
    #include <vector>
    using namespace std;
    
    vector<int> G[16005];
    int dp[16005],tot[16005];
    vector<int> ans;
    
    void dfs(int u)
    {
        tot[u] = 1;
        for(int i=0;i<G[u].size();i++)
        {
            int v = G[u][i];
            if(tot[v] == -1)
                dfs(v);
            else
                continue;
            dp[u] = max(dp[u],tot[v]);
            tot[u] += tot[v];
        }
    }
    
    int main()
    {
        int n,i,j,u,v;
        scanf("%d",&n);
        for(i=0;i<=n;i++)
            G[i].clear();
        for(i=0;i<n-1;i++)
        {
            scanf("%d%d",&u,&v);
            G[u].push_back(v);
            G[v].push_back(u);
        }
        memset(dp,0,sizeof(dp));
        memset(tot,-1,sizeof(tot));
        dfs(1);
        int mini = Mod;
        for(i=1;i<=n;i++)
        {
            int tmp = max(dp[i],n-tot[i]);
            if(tmp < mini)
            {
                ans.clear();
                ans.push_back(i);
                mini = tmp;
            }
            else if(tmp == mini)
                ans.push_back(i);
        }
        printf("%d %d
    ",mini,ans.size());
        sort(ans.begin(),ans.end());
        printf("%d",ans[0]);
        for(i=1;i<ans.size();i++)
            printf(" %d",ans[i]);
        puts("");
        return 0;
    }
  • 相关阅读:
    spring boot下载本地静态文件最实用
    非常实用的MySQL中if、ifnull函数以及case/when的使用
    java获取访问地址IP的简单方法
    Oracle数据库视图的创建以及使用
    http-post调用接口简单代码
    orale数据库to_char时间中英文转换
    java线程的简单实用
    java小数保留位数四舍五入
    二项式反演
    学习总结-后缀数组
  • 原文地址:https://www.cnblogs.com/Invictus-Ocean/p/12833487.html
Copyright © 2011-2022 走看看