树型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 ,它跟其他结点的距离可以划分成三部分,见下图:

树型DP[专题]

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 ;
}

 

 

你可能感兴趣的:(dp)