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的测评系统更好一点