树型DP[专题]

一、经典题系列。

1.选课

简要描述:从n门课中选出m门课(m<=n),每一门课有一门(或没有)先修课,每门课都有对应的学分,求最大学分。

分析:n门课程按照是否是先修的关系构成一个森林(即树型),设计状态d[i][j]:以结点i为根的子树选j门课能得到的最大学分值。根结点的状态由孩子结点得到,为了状态转移的方便,将多叉树转换成二叉树(孩子-兄弟表示法),这样孩子结点就变成了两个,转移方程是:

d[i][j] = max(d[ right[i] ][j], max( d[ left[i] ][k] + d[ right[i] ][j-k-1] + scr[i] )(0<=k<j))

需要思考的有两点:

1.右孩子表示的是根结点的兄弟结点,那么状态d[i][j]的意义跟最初的意义不同了,d[i][j]:表示将k门课分给 i 的孩子结点,将 j-i-1门课分给一个兄弟结点,但是,这两个结点是转换成二叉树后 i 的左右孩子结点,想想为什么这样是对的?

2. 转移方程中用红色标记的是边界情况,即结点 i 分到了 0 门课,j 门课都分给了兄弟结点。

 

选课
#include <stdio.h>
#include
<string.h>
#include
<stdlib.h>
#include
<ctype.h>
#include
<math.h>
#include
<set>
#include
<map>
#include
<queue>
#include
<vector>
#include
<algorithm>

using namespace std;

#define SUPERBIN
#define NL1 311
#define NL2 21
#define EP 1e-10
#define MAX(x,y) ((x)>(y)?(x):(y))
#define MIN(x,y) ((x)<(y)?(x):(y))
#define LL long long

int d[NL1][NL1], left[NL1], right[NL1], scr[NL1];
int n, m;
int max(int x, int y)
{
return x>y?x:y;
}
void DFS(int u) {
int i, j, k;
if (left[u] > 0) DFS(left[u]);
if (right[u] > 0) DFS(right[u]);
for (i = 1; i <= m; i++) {
for (j = 0; j < i; j++) {
d[u][i]
= max(d[u][i], d[left[u]][j] + d[right[u]][i - j - 1] + scr[u]);
// printf("d[%d][%d] = %d\n", u, i, d[u][i]); //test
}
d[u][i]
= max(d[u][i], d[right[u]][i]);
}
}

int main() {

int T, cs = 1;
int i, j, k, a, b, c, x, y, z;
scanf(
"%d%d", &n, &m);
for (i = 1; i <= n; i++) {
scanf(
"%d%d", &a, &scr[i]);
right[i]
= left[a]; //多叉转二叉
left[a] = i; //多叉转二叉
}
DFS(left[
0]);
printf(
"%d\n", d[left[0]][m]);

return 0;
}

 

类似的题:

hdu-1561

hdu-1561
#include <stdio.h>
#include
<string.h>
#include
<stdlib.h>
#include
<ctype.h>
#include
<math.h>
#include
<set>
#include
<map>
#include
<queue>
#include
<vector>
#include
<algorithm>

using namespace std;

//#define SUPERBIN
#define NL1 201
#define NL2 21
#define EP 1e-10
#define MAX(x,y) ((x)>(y)?(x):(y))
#define MIN(x,y) ((x)<(y)?(x):(y))
#define LL long long

int d[NL1][NL1], ch[NL1], sib[NL1], v[NL1], ct[NL1];
int n, m;

void DFS(int k)
{
if (ch[k]) DFS(ch[k]);
if (sib[k]) DFS(sib[k]);
d[k][
1] = v[k];
int i, j;
for (i=1; i<=m; i++) {
for (j=0; j<i; j++) {
d[k][i]
= MAX(d[k][i], d[ch[k]][j]+d[sib[k]][i-j-1]+v[k]);
}
d[k][i]
= MAX(d[k][i], d[sib[k]][i]);
}
}

int main() {
#ifdef SUPERBIN
freopen(
"in.txt", "r", stdin);
freopen(
"out.txt", "w", stdout);
#endif
int T, t, r, cs = 1;
int i, j, k, a, b, c, x, y, z;
while (scanf("%d%d", &n, &m) != EOF) {
if (!n && !m) break;
memset(ch,
0, sizeof(ch));
memset(sib,
0, sizeof(sib));
memset(d,
0, sizeof(d));
for (i=1; i<=n; i++) {
scanf(
"%d%d", &a, &v[i]);
sib[i]
= ch[a];
ch[a]
= i;
}
DFS(ch[
0]);
printf(
"%d\n", d[ch[0]][m]);
}
return 0;
}
/*
* 同‘选课’,跑了250ms有待改进
*/

 

2.Anniversary Party
简要描述:公司要举行宴会,要求被邀请的员工不能有直接上司-下属关系。

分析:对于结点 i ,分邀请和不邀请两种情况,设d[i][0]表示不邀请,d[i][1]表示邀请,则:

d[i][0] = Sum(max(d[j][0], d[j][1])(j是i的孩子结点)),

d[i][1] = Sum(d[j][0])。

PS:实现的时候还是转换成二叉树。

 

Anniversary party
#include <stdio.h>
#include
<string.h>
#include
<stdlib.h>
#include
<ctype.h>
#include
<math.h>
#include
<set>
#include
<map>
#include
<queue>
#include
<vector>
#include
<algorithm>

using namespace std;

#define SUPERBIN
#define NL1 6011
#define NL2 21
#define EP 1e-10
#define MAX(x,y) ((x)>(y)?(x):(y))
#define MIN(x,y) ((x)<(y)?(x):(y))
#define LL long long

int d[NL1][2], left[NL1], right[NL1], v[NL1];
bool flg[NL1];
int n;

int max(int x, int y) {
return x > y ? x : y;
}

void DFS(int u) {
int son = left[u];
d[u][
1] = v[u];
// printf("u = %d\n", u); //test
while (son > 0) {
// printf("son = %d\n", son); //test
DFS(son);
d[u][
0] += MAX(d[son][1], d[son][0]);
d[u][
1] += d[son][0];
// printf("d[%d][0] = %d, d[%d][1] = %d\n", u, d[u][0], u, d[u][1]); //test
son = right[son];
}
}

int main() {
#ifndef ONLINE_JUDGE
freopen(
"in.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
#endif
int T, cs = 1;
int i, j, k, a, b, c, x, y, z;
while (scanf("%d", &n) != EOF) {
v[
0] = 0;
for (i = 1; i <= n; i++)
scanf(
"%d", &v[i]);
memset(left,
-1, sizeof (left));
memset(right,
-1, sizeof (right));
memset(d,
0, sizeof (d));
memset(flg,
0, sizeof (flg));
while (scanf("%d%d", &a, &b)) {
if (!a && !b) break;
right[a]
= left[b];
left[b]
= a;
flg[a]
= 1;
}
for (i = 1; i <= n; i++) {
if (!flg[i]) {
right[i]
= left[0];
left[
0] = i;
}
}
DFS(
0);
printf(
"%d\n", MAX(d[0][0], d[0][1]));
}
return 0;
}

 

二、较复杂

1.computer

简要描述:n个电脑组成一个树形的网络,求各个电脑跟其他电脑通信的最远距离。

分析:在网上参考了一些解题报告,但感觉讲的都不太明白,不过还是想通了。对于某个结点 i ,它跟其他结点的距离可以划分成三部分,见下图:

d[i]表示:以 i 为根的子树,i 的子孙结点到 i 的最长距离,

d1[i]表示:以 i 的兄弟结点为根的子树,其子孙结点到 i 的父结点的最长距离,

d2[i]表示:i 的父结点 以上(即除去i的父结点为根的子树)的结点到 父结点的最长距离。

则 f[i] = max( d[i], max(d1[i], d2[i])+dis[i] )

ps:以上的符合表示的含义可能跟程序中的有出入。

还是喜欢用孩子-兄弟表示法,O(∩_∩)O~

 

computer
#include <stdio.h>
#include
<string.h>
#include
<stdlib.h>
#include
<ctype.h>
#include
<math.h>
#include
<set>
#include
<map>
#include
<queue>
#include
<vector>
#include
<algorithm>

using namespace std;

//#define SUPERBIN
#define NL1 10001
#define NL2 21
#define EP 1e-10
#define MAX(x,y) ((x)>(y)?(x):(y))
#define MIN(x,y) ((x)<(y)?(x):(y))
#define LL long long

int ch[NL1], sib[NL1], p[NL1], dis[NL1], d[NL1], cd[NL1], d1[NL1], d2[NL1];
int n;

void DFS_1(int k) {
int k0 = ch[k], t;
while (k0) {
DFS_1(k0);
t
= d[k0] + dis[k0];
if (t > d[k]) {
d1[k]
= d[k];
d[k]
= t;
cd[k]
= k0;
}
k0
= sib[k0];
}
k0
= ch[k];
while (k0) {
t
= d[k0] + dis[k0];
if (k0 != cd[k] && d1[k]<t) {
d1[k]
= t;
}
k0
= sib[k0];
}
}

void DFS_2(int k) {
int k1 = ch[k];
while (k1) {
if (k1 == cd[k]) {
d2[k1]
= MAX(d2[k], d1[k]) + dis[k1];
}
else {
d2[k1]
= MAX(d2[k], d[k]) + dis[k1];
}
DFS_2(k1);
k1
= sib[k1];
}
}

int main() {
#ifdef SUPERBIN
freopen(
"in.txt", "r", stdin);
freopen(
"out.txt", "w", stdout);
#endif
int T, t, r, cs = 1;
int i, j, k, a, b, c, x, y, z;
while (scanf("%d", &n) != EOF) {
memset(ch,
0, sizeof (ch));
memset(sib,
0, sizeof (sib));
memset(p,
0, sizeof (p));
memset(d,
0, sizeof (d));
memset(d1,
0, sizeof (d1));
memset(d2,
0, sizeof (d2));
for (i = 2; i <= n; i++) {
scanf(
"%d%d", &a, &dis[i]);
sib[i]
= ch[a];
ch[a]
= i;
}
DFS_1(
1);
DFS_2(
1);
/* for (i=1; i<=n; i++) printf("d[%d] = %d\n", i, d[i]); //test
for (i=1; i<=n; i++) printf("cd[%d] = %d\n", i, cd[i]); //test
for (i=1; i<=n; i++) printf("d1[%d] = %d\n", i, d1[i]); //test
for (i=1; i<=n; i++) printf("d2[%d] = %d\n", i, d2[i]); //test
*/
for (i = 1; i <= n; i++) {
printf(
"%d\n", MAX(d[i], d2[i]));
}
}
return 0;
}

 

 

posted @ 2010-10-03 18:38  superbin  阅读(518)  评论(0编辑  收藏  举报