zoukankan      html  css  js  c++  java
  • 树形DP入门

    一、基本概念

           树形DP,即在树上进行DP。一般都用递归的形式进行实现,根据叶子节点的信息对根节点进行DP。

    二、经典问题

           1、树的重心

                 重心的定义:若删去树中的一个点,使得树中各联通块中的最大连通块的结点数最小,则称这个点为树的重心。

                 实现方法:将无根树转化为有根树,用dfs处理出每个点的的子树大小(size)以及它的所有子树的大小的最大值(maxv)。之后枚举每个点,那么删去它后分成的连通块大小的最大值便是max( maxv[i] , n-size[i] )。

    例题:poj1655(模板题,没什么好说的),代码如下:

     1 #include<cstdio>
     2 #include<vector>
     3 using namespace std;
     4 
     5 const int MAXN=20010;
     6 int t,n,size[MAXN],maxv[MAXN],ansx,ansy;
     7 vector <int> ve[MAXN];
     8 
     9 int read(void) {
    10     char c; while (c=getchar(),c<'0' || c>'9'); int x=c-'0';
    11     while (c=getchar(),c>='0' && c<='9') x=x*10+c-'0'; return x;
    12 }
    13 
    14 void dfs(int u,int pre) { //dfs处理出每个点的size与maxv 
    15     size[u]=1;
    16     for (int i=0;i<ve[u].size();++i) {
    17       int v=ve[u][i];
    18         if (v==pre) continue;
    19       dfs(v,u);
    20       size[u]+=size[v];
    21       maxv[u]=max(maxv[u],size[v]);
    22     }
    23 }
    24 
    25 int main() {
    26     t=read();
    27       while (t--) {
    28           ansy=2e9;
    29           n=read();
    30             for (int i=1;i<=n;++i) ve[i].clear(),size[i]=0;
    31             for (int i=1;i<n;++i) {
    32                   int x=read(),y=read();
    33                   ve[x].push_back(y);
    34                   ve[y].push_back(x);
    35             }
    36         dfs(1,0);
    37           for (int i=1;i<=n;++i) { //枚举每个点作为重心 
    38               int tmp=max(n-size[i],maxv[i]);
    39                 if (ansy>tmp) {
    40                       ansy=tmp; ansx=i;
    41                 }
    42           }
    43         printf("%d %d
    ",ansx,ansy);
    44       }
    45     return 0;
    46 } 

           2、树中的最长链

                 最长链的定义:找到两个点,使得这两个点的距离最远,则称这两个点之间的路径为最长链。

                 实现的方法:从任意一个点(一般为树根)开始dfs/bfs,找出距离这个点最远的点u。之后再进行一次dfs/bfs,找出离u最长的点v,则u到v的路径则为树的直径。

                 方法的证明:若起始点为树的最长链上的点,则进行两次寻找后找到的便是最长链上的两端。若起始点不为树的最长链上的点,则设起始点到点u之间的距离为l1,起始点到最长链上某一点(x)的距离为l2,点x到最长链另一端(两段中较长的一端)的距离为l3,最长链的长度为L。若点u不是最长链上的点,则可得 l1 > l2 + l3。假设起始点到u的路径与最长链有交,则 l1 - l2 > l3,即链上某一点到点u的距离大于链上某一点到链上距离较长端的距离,而 l3 >= L/2,所以 l1 - l2 > L/2,这与假设矛盾(l1 + l3的距离也是一条链的距离,而l3 > L/2,则 l1 > l/2 + l2 > L,这与L为最长链的长度的假设不符),所以 l1 + l2 > l3不成立。假设起始点到u的路径与最长链无交,即在以起始点为根的子树中,u与最长链处于不同的子树那最长链应为一条过起始点的最长链,与最长链的假设不符。所以,在一次dfs/bfs后找到的必为最长链上的某一点。

    例题:poj1985(路的方向没有什么用,之间跑一遍树的最长链就好了),代码如下:

     1 #include<cstdio>
     2 #include<vector>
     3 #define mp make_pair
     4 #define fir first
     5 #define sec second
     6 #define pa pair <int,int>
     7 using namespace std;
     8 
     9 const int MAXN=40010;
    10 int n,m,dep[MAXN],inx;
    11 vector <pa> ve[MAXN];
    12 
    13 int read(void) {
    14     char c; while (c=getchar(),c<'0' || c>'9'); int x=c-'0';
    15     while (c=getchar(),c>='0' && c<='9') x=x*10+c-'0'; return x;
    16 }
    17 
    18 void dfs(int u,int pre) { //处理出每个点的距离 
    19     for (int i=0;i<ve[u].size();++i) {
    20       int v=ve[u][i].fir;
    21         if (v==pre) continue;
    22       dep[v]=dep[u]+ve[u][i].sec;
    23       dfs(v,u);
    24     }
    25 }
    26 
    27 int main() {
    28     n=read(); m=read();
    29       for (int i=1;i<=m;++i) {
    30           int x=read(),y=read(),v=read();
    31           ve[x].push_back(mp(y,v)); //不要被路的方向迷惑了,要双向建边 
    32           ve[y].push_back(mp(x,v));
    33       }
    34     dfs(1,0);
    35       for (int i=1;i<=n;++i) 
    36         if (dep[i]>dep[inx]) inx=i;
    37       for (int i=1;i<=n;++i) dep[i]=0;
    38     dfs(inx,0); inx=0;
    39       for (int i=1;i<=n;++i)
    40         if (dep[i]>dep[inx]) inx=i;
    41     printf("%d",dep[inx]);
    42     return 0;
    43 }

           3、树的中心

                树的中心的定义:树上的点到其它端点最大距离的值最小的点。

                实现的方法:对于每个点,设up[i]表示除i的叶子节点外,其他节点到这个点的距离最大值;d1[i]表示这个点的叶子节点中到这个点的距离最大值;d2[i]表示这个点的叶子节点中到这个点的距离次大值;c1[i]表示叶子节点最大值所在的子树;c2[i]表示叶子节点次大值所在的子树;maxv[i]表示这个点到其他点距离的最大值。进行两次dfs,在第一次中求出每个点的d1,d2,c1,c2。假设节点u的叶子节点为v,若d1[v] + dist[v][u] > d1[u],则d2[u] = d1[u],c2[u] = c1[u],d1[u] = d1[v] + dist[v][u],c1[u] = v。若d1[v] + dist[v][u] > d2[u],则d2[u] = d1[v] + dist[v][u],c2[u] = v(不需要考虑d2[v] + dist[v][u]与d2[u]的关系,因为d1与d2均为u的子树中的最大与次大,所以只需比较d1即可)。之后再进行一次dfs,处理出每个结点的up值,设u是v的父节点,若c1[u] == v,则up[v] = max( d2[u] , up[u] ) + dist[u][v],若c1[u] != v,则up[v] = max( d1[u] , up[u] ) + dist[u][v]。最后每个结点距它最远的结点的距离为max( up[i] , d1[i] )。

    例题:hdu2196,代码如下:

     1 #include<cstdio>
     2 #include<vector>
     3 #include<algorithm>
     4 #include<cstring>
     5 #define pa pair <int,int>
     6 #define fir first
     7 #define sec second
     8 #define mp make_pair
     9 using namespace std;
    10 
    11 const int MAXN=10010;
    12 int n,d1[MAXN],d2[MAXN],c1[MAXN],c2[MAXN],up[MAXN],maxv[MAXN];
    13 vector <pa> ve[MAXN];
    14 
    15 int read(void) {
    16     char c; while (c=getchar(),c<'0' || c>'9'); int x=c-'0';
    17     while (c=getchar(),c>='0' && c<='9') x=x*10+c-'0'; return x;
    18 }
    19 
    20 void dfs1(int u,int pre) {
    21     for (int i=0;i<ve[u].size();++i) {
    22       int v=ve[u][i].fir;
    23         if (v==pre) continue;
    24       dfs1(v,u);
    25         if (d1[v]+ve[u][i].sec>d1[u]) { //处理出最大与次大 
    26           d2[u]=d1[u]; c2[u]=c1[u];
    27           d1[u]=d1[v]+ve[u][i].sec; c1[u]=v;
    28         } 
    29         else if (d1[v]+ve[u][i].sec>d2[u]) {
    30           d2[u]=d1[v]+ve[u][i].sec; c2[u]=v;
    31         }
    32         //无需考虑d2[v]与d2[u]的大小关系 
    33     }
    34 }
    35 
    36 void dfs2(int u,int pre) {
    37     for (int i=0;i<ve[u].size();++i) {
    38       int v=ve[u][i].fir;
    39         if (v==pre) continue; //处理出up 
    40         if (v==c1[u]) up[v]=max(up[u],d2[u])+ve[u][i].sec;
    41         else if (v!=c1[u]) up[v]=max(up[u],d1[u])+ve[u][i].sec;
    42       dfs2(v,u);
    43     }
    44 }
    45 
    46 void init(void) {
    47     memset(d1,0,sizeof(d1));
    48     memset(d2,0,sizeof(d2));
    49     memset(up,0,sizeof(up));
    50     for (int i=1;i<=n;++i) ve[i].clear();
    51 }
    52 
    53 int main() {
    54     while (scanf("%d",&n)!=EOF) {
    55       init();
    56         for (int i=2;i<=n;++i) {
    57             int x=read(),y=read();
    58             ve[i].push_back(mp(x,y));
    59             ve[x].push_back(mp(i,y));
    60         }
    61       dfs1(1,0);
    62       dfs2(1,0);
    63         for (int i=1;i<=n;++i) maxv[i]=max(up[i],d1[i]);
    64         for (int i=1;i<=n;++i) printf("%d
    ",maxv[i]);
    65     }
    66     return 0;
    67 } 

           4、树上背包

                 定义:树上背包,顾名思义,即在树上进行背包(好像没什么好说的)。

    例题:CTSC1997选课(https://www.luogu.org/problemnew/show/P2014

    解析:加入一个虚拟的结点0连向每一棵树的根,则可将多棵树转化为一课有根树。设dp[u][j]表示以i为根的子树中选j个所获得的最大学分,则可在树上进行分组背包。枚举u的所有子树v以及子树中选了k个,则dp[u][j] = max( dp[u][j] , dp[u][j-k] + dp[v][k] )。注意要在dp的最后加上节点u的学分。(注意!n<=300)。

    代码如下:

     1 #include<cstdio>
     2 #include<vector>
     3 #include<algorithm>
     4 using namespace std;
     5 
     6 const int MAXN=301;
     7 int n,m,val[MAXN],dp[MAXN][MAXN];
     8 vector <int> ve[MAXN];
     9 
    10 int read(void) {
    11     char c; while (c=getchar(),c<'0' || c>'9'); int x=c-'0';
    12     while (c=getchar(),c>='0' && c<='9') x=x*10+c-'0'; return x;
    13 }
    14 
    15 void dfs(int u) {
    16     dp[u][0]=0; //选0个课程学分为0 
    17     for (int i=0;i<ve[u].size();++i) {
    18       int v=ve[u][i];
    19       dfs(v);
    20         for (int j=m;j>=0;--j) //枚举u选了几个 
    21           for (int k=j;k>=0;--k) //枚举v选了几个 
    22             dp[u][j]=max(dp[u][j],dp[u][j-k]+dp[v][k]);
    23     }
    24     if (u) { //加上u节点本身的学分 
    25       for (int j=m;j>0;--j) dp[u][j]=dp[u][j-1]+val[u];
    26     }
    27 }
    28 
    29 int main() {
    30     n=read(); m=read();
    31       for (int i=1;i<=n;++i) {
    32           int x=read(); val[i]=read();
    33           ve[x].push_back(i);
    34       }
    35     dfs(0);
    36     printf("%d",dp[0][m]);
    37     return 0;
    38 } 
  • 相关阅读:
    tar命令,vi编辑器
    Linux命令、权限
    Color Transfer between Images code实现
    利用Eclipse使用Java OpenCV(Using OpenCV Java with Eclipse)
    Matrix Factorization SVD 矩阵分解
    ZOJ Problem Set
    Machine Learning
    ZOJ Problem Set
    ZOJ Problem Set
    ZOJ Problem Set
  • 原文地址:https://www.cnblogs.com/Gaxc/p/9741308.html
Copyright © 2011-2022 走看看