一、经典题系列。
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 ;
}
类似的题:
#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:实现的时候还是转换成二叉树。
#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~
#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 ;
}