题目链接:http://poj.org/problem?id=1192
题意:给出一个由平面整点构成的树(两点相邻当且仅到曼哈顿距离为1),有N个点,每个点有权值。求这棵树的权值最大的子树(只求这个子树的权值)
思路:用树上DP解决。称所要求的权值最大子树为“最大集”
首先要把这个树看成一个有根树。对于每个节点,维护两个数,分别是该子树中最大集的权值,和包含当前这个节点的最大集的权值。对于根来说,前一个数就是要求的答案。而后一个数是为了合并一个节点的各个子树中的答案。如果在某个子树中最大集不包含根节点则在上一层中无法将这个最大集合并。
状态转移:只需注意到一棵树的最大集或者是一个子树的最大集(对应维护的第一个数),或者是包含根节点的最大集(对应第二个)。而包含根节点的最大集,除去根节点之外还包含它所有子树的包含(对应子树的)根节点且权值为正的最大集(如果权值为负,则总贡献是负的)。
复杂度:因为每个点的邻居最多4个(平面整点),复杂度是O(N)。
实现:使用DFS。在遍历所有邻居时要去除上一层的点,使用vis[]来标记。
代码如下:
/*
PROG: POJ1192
PROB:
*/
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
#define DEBUG 1
#define LOG(...) do { if (DEBUG) fprintf(stderr, __VA_ARGS__); } while(0)
#define MAXN 1005
int M[MAXN][MAXN];
char vis[MAXN][MAXN];
int N, val[MAXN];
struct Point {
int x, y;
Point(int x=0, int y=0) : x(x), y(y) {}
};
typedef vector<Point> VP;
typedef pair<int, int> PII;
const int dx[] = {1, -1, 0, 0};
const int dy[] = {0, 0, 1, -1};
PII wk(int x, int y) {
PII ret(-10000000, -10000000);
vis[x][y] = 1;
int curr = val[M[x][y]];
for (int dir = 0; dir < 4; ++dir) {
int tx = x+dx[dir], ty = y+dy[dir];
if (tx<0||ty<0||tx>=MAXN||ty>=MAXN) continue;
if (vis[tx][ty]||M[tx][ty]<0) continue;
PII tmp(wk(tx,ty));
ret.first = max(ret.first, tmp.first);
if (tmp.second>0) curr += tmp.second;
}
ret.second = curr;
ret.first = max(ret.first, ret.second);
vis[x][y] = 0;
return ret;
}
int main(void) {
scanf("%d", &N);
VP pts(N);
int x, y, mx, my; scanf("%d%d%d", &mx, &my, val);
pts[0] = Point(mx, my);
for (int i = 1; i < N; ++i) {
scanf("%d%d%d", &x, &y, val+i);
pts[i] = Point(x, y);
mx = min(mx, x);
my = min(my, y);
}
memset(M, -1, sizeof(M));
// LOG("%d, %d\n", mx, my);
for (int i = 0; i < N; ++i) {
pts[i].x -= mx, pts[i].y -= my;
M[pts[i].x][pts[i].y] = i;
// LOG("%d %d\n", pts[i].x, pts[i].y);
}
PII ans = wk(pts[0].x, pts[0].y);
printf("%d\n", ans.first);
return 0;
}