poj1192(树形dp)

感谢涛涛不解

这道题着实花了很长时间才搞明白,对图的知识忘得太多了,看这道题之前最好先把图的邻接表表示方法看下,有助于对这道题的理解,这道题让我对深度优先遍历又有了进一步的了解

这道题最好画个图,有助于理解,不过你要是画个错误的图此题就无解了

其实就是一个求无向树的所有子树和的最大值

树形dp

dp[i][0]表示以i为根,不包括i结点的子树获得最大权

dp[i][1]表示以i为根,包括i结点的子树获得的最大权

dp[i][0] = max(dp[k][0], dp[k][1]) k为i的所有孩子结点

dp[i][1] = i结点的权 + dp[k][1] 如果dp[k][1] > 0

  1 #include <stdio.h>
  2 #include <cstring>
  3 #include <stdlib.h>
  4 
  5 using namespace std;
  6 
  7 struct node //这个名字起得不是很恰当,说是结点,其实是边,本题建的是有向边
  8 {
  9     int u;//边所指向的结点
 10     int next;//与改变共起点的边,和邻接表表示图的方法差不多,只不过这里用的是一个int型的变量而不是一个指针
 11 };
 12 
 13 
 14 struct Point
 15 {
 16     int x;
 17     int y;
 18     int c;
 19 };
 20 Point point[1015];
 21 int head[1015];//head[1]是以1开头的边
 22 node edge[1015*10];//表示边
 23 int visited[1015];
 24 int n;
 25 int count=0;
 26 int dp[1015][2];
 27 
 28 
 29 
 30 void init()
 31 {
 32     memset(head,-1,sizeof(head));//把以任何一个点的开始的边都设置为一个无效的变量
 33 }
 34 
 35 void input()
 36 {
 37     int i;
 38     scanf("%d",&n);
 39     for(i=0;i<n;i++)
 40     {
 41         scanf("%d %d %d",&point[i].x,&point[i].y,&point[i].c);
 42     }
 43 }
 44 
 45 
 46 void addEdge(int c ,int d)
 47 {
 48     edge[count].u=d;//这是一条有向边
 49     edge[count].next=head[c];
 50     head[c]=count++;
 51 
 52     edge[count].u=c;//这是另一条有向边
 53     edge[count].next=head[d];
 54     head[d]=count++;
 55 }
 56 
 57 
 58 void buildTree()
 59 {
 60     int i,j;
 61     for(i=0;i<n;i++)
 62     {
 63         for(j=i+1;j<n;j++)
 64         {
 65             if((abs(point[i].x - point[j].x) + abs(point[i].y - point[j].y)) == 1)
 66             {
 67                 addEdge(i,j);//添加一对有向边
 68             }
 69         }
 70     }
 71 }
 72 
 73 int max(int a,int b)
 74 {
 75     return a>b?a:b;
 76 }
 77 
 78 
 79 void dfs(int u)
 80 {
 81     visited[u]=1;
 82     dp[u][0]=0;
 83     dp[u][1]=point[u].c;
 84     for(int i=head[u];i!=-1;i=edge[i].next)
 85     {
 86         int v=edge[i].u;
 87         if(visited[v]==0)
 88         {
 89             dfs(v);
 90 
 91             dp[u][0]=max(dp[u][0],max(dp[v][0],dp[v][1]));//注意权值有正有负,这个语句即更新dp[u][0]
 92             if(dp[v][1]>0)//当改点不选时就没有必要更新,因为值为1,当选的时候要更新该点,因为父节点要用到子结点的值
 93             {
 94                 dp[u][1]=dp[u][1]+dp[v][1];//这个语句更新dp[u][i],更新的方法其实差不多,u是父节点,v是子节点,当选u节点时,要加上其儿子,若不选u节点时,比较自己和儿子节点
 95             }
 96         }
 97     }
 98 
 99 }
100 
101 
102 int main()
103 {
104     init();//初始化操作
105     input();//解决输入问题
106     buildTree();//构造树,采用邻接表的形式
107     dfs(0);// 深度优先遍历
108     printf("%d\n",max(dp[0][0],dp[0][1]));
109     return 0;
110 }

 

edge数组是用来保存边集的,
head数组是邻接表,e初始为0,比如要这里建立u->v和v->u的双向边,先在head的这个点加入这条边,edge[e].u = v表示这条边连的点时v,edge[e].next = head 相当于一个链表,指向下一个节点,也是一个边,这个边要么是空的,要么是与u相连的边集,然后head = e++,表示这个点指向这个边集的头,e++自增是一个空间给下个边使用。。。

 

 

 

dp[0] = max(dp[0], max(dp[v][0], dp[v][1])); 
if(dp[v][1] > 0) 
dp[1] += dp[v][1]; 
这两句dp[0] = max(dp[0], max(dp[v][0], dp[v][1])); dp[0]表示以u为根的子树,0表示不包括u,v是u的邻接点,
比如你看下面 
u
v1 v2 v3
A1 A2 v1, v2, v3是u的儿子, A1, A2是v2的儿子
max(dp[v2][0], dp[v2][1])比方A1的权值为-1, A2为-2,v2为4
那么取最大值dp[v2][0]不包括v2的子树为A1值为-1,dp[v2][1]包括v2的子树为v2->A1值为3,那么max(dp[v2][0], dp[v2][1])就取了包括v2的情况。dp[0] = max(dp[0], max(dp[v][0], dp[v][1])); 因为dp[0]初始为0的,这样做就是为了求一个最大的max(dp[v][0], dp[v][1]));
对于if(dp[v][1] > 0) 
dp[1] += dp[v][1]; 
dp[1]表示以u为根节点包含u节点的子树,那么连通图必须u和它的儿子v相连,为了使他尽量的大,当然得加它儿子子树里面为正数的才能越来越大,0加了无效果,负数会小值,所以要大于0

 

另外还要说一点,刚开始把1000看成100了,数组开小了,poj报的是超时的错误,有点无语

不过hdu会报runtime error的,所以个人感觉hdu的测评系统更好一点

posted on 2012-08-10 12:30  矮人狙击手!  阅读(1279)  评论(0编辑  收藏  举报

导航