树状DP POJ 1192 最优连通图

感觉树状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])






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值