您的位置:首页 > 其它

pku 树形DP 1848 Tree 解题报告

2009-11-20 16:27 567 查看
pku 树形DP 1848 Tree 解题报告
此题好难,但很有意义,能让自己对树形dp有了更深一层的认识.在此十分感谢:JNU zorro的解题报告:http://blog.sina.com.cn/s/blog_5c95cb070100d8gu.html.讲解的很仔细。我就在这里做个总结。
描述:给一棵N个节点的树添加一些边,使得所有的点在且仅在唯一一个环,环至少要3个点。求添加边数的最小值。
思路: 我们考虑根所在的环,这个环从根出发一定要从进入一个子树A然后从子树A出来进入子树B然后从子树B回到根,A和B可以是同一个子树,只能经过这2个子树或者一个子树(A和B相同的情况),因为如果经过第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,k为x的儿子)
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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: