感觉树状DP都和 dfs 有关..
对我来说主要是建树有困难..
题意:给n个點..如果符合横坐标的差和纵坐标的差之和为1就是连通..求连通图的最大值..
思路:
树状DP..
先建树..然后就是简单的DP了..
我们对于最大值的计算有两种方案:1.记录法,2.过程法
记录法就是每个子树的根节点(包括叶子节点)记录dp[i][0]与dp[i][1],前一个表示不包含根的最大值,后一个表示包含根的最大值。那么我们可以得到对于dp[i][0],必然是所有分支中dp[child][0]与dp[child][1]中大于0的最大值的累加(因为不包含树根,所以在根节点上的连通性不用保证),dp[i][1]必然是所有分支中dp[child][1]中大于0的最大值的累加再加上该树根本身的值(因为要保证连通性)。最后只要比较dp[start][0]与dp[start][1],输出较大的一个即可。
过程法是基于这样一种理解:我们的最大权值和必定会出现在dp过程中的某一个子树(该子树包含根节点)中,因为任何一个分支我们都可以看成一个子树,任何一个无根的子树我们都可以看成一些不相交子树的集合。理解了这点,我们就可以在dp过程中设置全局变量max进行比较,最后得到的max就是所求最大权值和。
然后这道题我用的方法就是..
建树:
用一个动态二维数组..第一维是树根..第二维是子节点..
读完数据后遍历所有點..符合条件就把它加入到动态数组里..
然后对某一个點进行dfs..用标记数组visitedp[] 就可以进行后序遍历..
当到了叶结点的时候就让叶结点的值等于本身的权值..
然后返回上一层的时候通过转移方程..dp[r][0] = max(dp[r][0], max(dp[tmp][0], dp[tmp][1]));
if(dp[tmp][1] > 0) dp[r][1] += dp[tmp][1];
算出该點的dp[][0] 和 dp[][1] 了~最后看看哪个比较大就可以得到最优解..
Code:
#include <stdio.h>
#include <vector>
#include <cstring>
#include <cmath>
#define max(a, b) a>b?a:b
using namespace std;
struct Point
{
int x;
int y;
int c;
}p[1010];
bool visited[1010];
int dp[1010][2];
vector<int> con[1010];
bool near(Point a, Point b)
{
return fabs(a.x - b.x) + fabs(a.y - b.y) == 1;
}
void dfs(int r)
{
int tmp;
visited[r] = true;
dp[r][0] = 0;
dp[r][1] = p[r].c;
for(int i = 0; i < con[r].size(); ++i){ ///build the tree
if(!visited[con[r][i]]){
tmp = con[r][i];
dfs(tmp);
dp[r][0] = max(dp[r][0], max(dp[tmp][0], dp[tmp][1]));
if(dp[tmp][1] > 0) dp[r][1] += dp[tmp][1];///because it is a contected graph
}
}
}
int main()
{
int n;
int i, j, k;
while(scanf("%d", &n) != EOF)
{
memset(visited, false, sizeof(visited)); ///reset all variable array
memset(dp, 0, sizeof(dp));
for(i = 0; i <= n; ++i)
con[i].clear();
for(i = 0; i < n; ++i){
scanf("%d %d %d", &p[i].x, &p[i].y, &p[i].c);
}
for(i = 0; i < n; ++i)
for(j = i+1; j < n; ++j){
if(near(p[i], p[j])){
con[i].push_back(j);
con[j].push_back(i);
}
}
dfs(0);
printf("%d\n", max(dp[0][0], dp[0][1]));
}
return 0;
}
这个代码虽然简单..但是感觉逻辑不是很清晰..
个人比较喜欢 之路 的做法..
贴一下他的Code:
/*********************************************
POJ1192最优连通子集
题目意思:如果两个点的距离相差一,则两个点相邻或着说连通。
求:连通图的最大加权和。
思路:对给出的 n 个点建树,然后就是灰常简单的树形DP了。
f[ p ] 表示节点 p 含有的最大值,最后用个for循环遍历各个节点找出最大加权和。
**********************************************/
#include<iostream>
#include<stdio.h>
#include<vector>
#include<math.h>
using namespace std;
const int MAXN=1005;
int n;
bool used[MAXN];
int f[MAXN];
struct Node
{
int x,y,v;
}tt[MAXN];
vector<int>child[MAXN];
bool near(int p,int q)//判断两个整点是否相邻
{
if(fabs((double)tt[p].x-tt[q].x)+fabs((double)tt[p].y-tt[q].y)==1)return true;
return false;
}
void dfs(int p)
{
used[p]=true;
for(int i=1;i<=n;i++)
{
if(!used[i]&&near(p,i))
{
child[p].push_back(i);
dfs(i);
}
}
}
void recur(int p)
{
if(child[p].size()==0)
{
f[p]=tt[p].v;
return;
}
for(int i=0;i<child[p].size();i++)
recur(child[p][i]);
f[p]=tt[p].v;
for(int i=0;i<child[p].size();i++)
if(f[child[p][i]]>0)f[p]+=f[child[p][i]];
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d%d%d",&tt[i].x,&tt[i].y,&tt[i].v);
for(int i=1;i<=n;i++) child[i].clear();
memset(used,false,sizeof(used));
dfs(1);
recur(1);
int ans=0;
//for(int i=1;i<=n;i++)
//if(f[i]>ans)ans=f[i];
printf("%d\n",f[1]);
system("pause");
return 0;
}
dfs 用来建树..
recur 第一个for循环用来对树进行后序遍历..使叶节点得到对应的权值..
第二个for循环用来对子树进行动态转移..
转移方程是if(f[child[p][i]]>0)f[p]+=f[child[p][i]] 就是如果该子节点权值> 0 就把它加入到当前节点上..
因为只要子节点的权值是正数..加上去就能让根节点的总权值增加..
最后根节点的值就是最优解了..
Tips:
con 第一维是树根 第二维是子节点 con的值表示该节点权值
dp[ ] [ ] 第一维是节点编号 第二维 0 表示不包含根节点 1 表示包含根节点 dp的值表示当前最优解值
初始条件 dp[ r ][ 0 ] = 0; dp[ r ][ 1 ] = pre[ r ].c
动态转移方程 dp[r][0] = max(dp[r][0], max(dp[tmp][0], dp[tmp][1]));
if(dp[tmp][1] > 0) dp[r][1] += dp[tmp][1];
结果: max(dp[0][0], dp[0][1])