pku 树形DP 1848 Tree 解题报告

 

pku 树形DP 1848 Tree 解题报告

此题好难,但很有意义,能让自己对树形dp有了更深一层的认识.在此十分感谢:JNU zorro的解题报告:http://blog.sina.com.cn/s/blog_5c95cb070100d8gu.html.讲解的很仔细。我就在这里做个总结。

描述:给一棵N个节点的树添加一些边,使得所有的点在且仅在唯一一个环,环至少要3个点。求添加边数的最小值。

思路: 我们考虑根所在的环,这个环从根出发一定要从进入一个子树A然后从子树A出来进入子树B然后从子树B回到根,AB可以是同一个子树,只能经过这2个子树或者一个子树(AB相同的情况),因为如果经过第3个子树C,那么就可以从C回到根,这样根就属于2个环,与题意矛盾。而且其他子树之间不能连边,因为只要一连,那么根就多了一个环!这样的结果是其他子树都要在自己内部连边来解决本树的问题,从而这些子树之间不会相互干扰,这个条件决定可以动态规划。于是我们任选两棵树作为入树和出树,哪个入那个出无所谓,因为没有方向,然后把所有他子树的最优值相加,得到这组入树出树的值,然后遍历所有入树出树的方案,数量是儿子数平方,求最值。但是我们还需要考虑一个情况:如果选择一个子树既作为入树又作为出树(出入树),那么从根出发到达子树的根,这是不能马上返回,因为如果这样,根所在的环只有2个点,根和子树的根,不行,必须再进入一层才可以返回。这个作为入树或者出树的情况不同。

于是我们需要子树的3个最值:

1.子树内部自己解决的最值。

2.子树作为入树或者出树的最值。

3.子树作为出入树的最值。

jnu zorro 就说得很明白了,对于树中的每一个顶点无非就仅有3种状态:

1dp[x][0]表示以x为根的树,变成每个顶点恰好在一个环中的图,需要连的最少边数。

2dp[x][1]表示以x为根的树,除了根x以外,其余顶点变成每个顶点恰好在一个环中的图,需要连的最少边数。

3dp[x][2]表示以x为根的树,除了根x以及和根相连的一条链(算上根一共至少2个顶点)以外,其余顶点变成每个顶点恰好在一个环中的图,需要连的最少边数。

那么我们就可以对此3种状态做出分析,得出其状态转移方程:

 

设根为x,那么:         (y,z,kx的儿子)

Case1:dp[x][1]=min(dp[x][1], [0])         

Case2:dp[x][2]=min(dp[x][2], [0]+min(dp[y][1],dp[y][2]))

Case3:dp[x][0]=min(dp[x][0], [0]+dp[y][2]+1)

Case4:dp[x][0]=min(dp[x][0], (k)[0]+min(dp[y][2],dp[y][1])+min(dp[z][2],dp[z][1])+1

 

AC代码:

#include <iostream>

using namespace std;

 

const int M = 110;

const int oo = 10000;

bool visit[M];

int N, size, dp[M][3];

//Tree

int tree[M][M];

 

int min(int a, int b)

{

       return a > b ? b : a;

}

 

void dfs(int now)

{

       visit[now] = true;

       int child[M][M] = {0};

       int i, j, k, sum;

 

       for (i = 1; i <= tree[now][0]; i++)

       {

              if (!visit[tree[now][i]])

              {

                     //一直搜索到树低,然后一层一层回溯,往上dp

                     dfs(tree[now][i]);

                     //由于tree是无向图,但要从底向上dp,那么应该变为有向图来处理,最高点为1

                     child[now][++child[now][0]] = tree[now][i];

              }

       }

       //如果是树低,那么一定没有儿子

       //dp处理4种情况

       if (child[now][0] == 0)

       {

              dp[now][0] = oo;

              dp[now][1] = 0;   

              dp[now][2] = oo;

       }

       //case 1:

       sum = 0;

       for (i = 1; i <= child[now][0]; i++)

       {

              sum += dp[child[now][i]][0];

       }

       dp[now][1] = min(dp[now][1], sum);

       for (i = 1; i <= child[now][0]; i++)

       {

              sum = 0;

              int p = min(dp[child[now][i]][1], dp[child[now][i]][2]);

              for (j = 1; j <= child[now][0]; j++)

              {

                     if (i != j)

                     {

                            sum += dp[child[now][j]][0];

                     }

              }

              //case 2:

              dp[now][2] = min(dp[now][2], sum + p);

              //case 3:

              dp[now][0] = min(dp[now][0], sum + dp[child[now][i]][2] + 1);

       }

       //case 4:

       for (i = 1; i <= child[now][0]; i++)

       {

              for (j = i + 1; j <= child[now][0]; j++)

              {

                     sum = 0;

                     int p1 = min(dp[child[now][i]][1], dp[child[now][i]][2]);

                     int p2 = min(dp[child[now][j]][1], dp[child[now][j]][2]);

                     for (k = 1; k <= child[now][0]; k++)

                     {

                            if (k != i && k != j)

                            {

                                   sum += dp[child[now][k]][0];

                            }

                     }

                     dp[now][0] = min(dp[now][0], sum + p1 + p2 + 1);

              }

       }

}

 

int main()

{

       //freopen("1.txt", "r", stdin);

       int i, a, b;

 

       scanf("%d", &N);

       for (i = 1; i < N; i++)

       {

              scanf("%d%d", &a, &b);

              //利用链接表保存这棵树

              //tree[a][0]记录a有多少个儿子,tree[a][i]记录儿子

              tree[a][++tree[a][0]] = b;

              tree[b][++tree[b][0]] = a;

       }

       for (i = 1; i <= N; i++)

       {

              dp[i][0] = dp[i][1] = dp[i][2] = oo;

              visit[i] = false;

       }

       dfs(1);

       if (dp[1][0] == oo)

       {

              printf("-1/n");

       }

       else

       {

              printf("%d/n", dp[1][0]);

       }

       return 0;

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值