自己做了动态规划的题目已经有了一个月,但是成效甚微,所以来总结一下动态规划,希望自己能够温故知新。这个博客是关于树形dp的,动态规划的一类题目。

首先从最简单的树形DP入手,树形DP顾名思义就是一棵树和动态规划结合起来,我做了7,8题树形DP,目前为止发现树形DP的代码样式都是差不多,都在dfs树的过程中进行DP。

首先看一道简单的入门题目

题意就是在一棵树中,选取一些结点每个结点都可以监管者连接自己的一条边,问最少选取多少个结点可以让所有边都被监管起来。

思路:1:结点状态可以分为取和不取,所以用二维数组表示结点的状态。2:如果当前结点选取了,子结点可以选取也可以不选取,但是如果当前结点没有选取,那么子节点必须选取。

状态转移方程见代码里

#include <iostream>
#include <algorithm>
#include <string.h>
#include <math.h>
#include <stdlib.h> using namespace std;
#define MAX 1500
int n;
int root;
int tot;
struct Node
{
int value;
int next;
}edge[MAX*2+5];
int head[MAX+5];
int dp[MAX+5][2];
int vis[MAX+5];
void add(int x,int y)
{
edge[tot].value=y;
edge[tot].next=head[x];
head[x]=tot++;
}
void dfs(int root)
{
dp[root][0]=0;
dp[root][1]=1;
vis[root]=1;
for(int i=head[root];i!=-1;i=edge[i].next)
{
int k=edge[i].value;
if(!vis[k])
{
dfs(k);
//状态转移方程
dp[root][0]+=dp[k][1];
dp[root][1]+=min(dp[k][0],dp[k][1]);
}
}
}
int main()
{ int a,b;
int m;
while(scanf("%d",&n)!=EOF)
{
tot=0;
memset(vis,0,sizeof(vis));
memset(head,-1,sizeof(head));
memset(dp,0,sizeof(dp));
for(int i=0;i<n;i++)
{
scanf("%d:(%d)",&a,&m);
if(i==0) root=a;
for(int j=0;j<m;j++)
{
scanf("%d",&b);
add(a,b);
add(b,a);
}
}
dfs(root);
printf("%d\n",min(dp[root][0],dp[root][1]));
}
return 0;
}

这是一道入门题目,那么再看一到和它非常类似的题目,只是稍微变了一下

状态转移方程见代码里

(http://acm.hdu.edu.cn/showproblem.php?pid=1520)

#include <iostream>
#include <string.h>
#include <algorithm>
#include <math.h>
#include <stdlib.h> using namespace std;
#define MAX 6000
struct Node
{
int value;
int next;
}edge[MAX*2+5];
int tag[MAX+5];
int dp[MAX+5][2];
int n;
int root;
int tot;
int rat[MAX+5];
int head[MAX+5];
int vis[MAX+5];
void add(int x,int y)
{
edge[tot].value=y;
edge[tot].next=head[x];
head[x]=tot++;
}
void dfs(int root)
{
dp[root][1]=rat[root];
dp[root][0]=0;
vis[root]=1;
for(int i=head[root];i!=-1;i=edge[i].next)
{
int k=edge[i].value;
if(!vis[k])
{
dfs(k);
//状态转移方程
dp[root][1]+=dp[k][0];
dp[root][0]+=max(dp[k][0],dp[k][1]);
}
} }
int main()
{
int a,b;
while(scanf("%d",&n)!=EOF)
{
tot=0;
memset(head,-1,sizeof(head));
memset(dp,0,sizeof(dp));
memset(vis,0,sizeof(vis));
memset(tag,0,sizeof(tag));
for(int i=1;i<=n;i++)
{
scanf("%d",&rat[i]);
}
scanf("%d%d",&a,&b);
while(a!=0&&b!=0)
{
tag[a]=1;
add(b,a);
add(a,b);
scanf("%d%d",&a,&b);
}
for(int i=1;i<=n;i++)
{
if(tag[i]==0)
{
root=i;
break;
}
}
dfs(1);
printf("%d\n",max(dp[1][0],dp[1][1]));
}
return 0;
}

这两道题目可以说一个类型的,都是在树上选取一些结点,以获得最优值,限制条件就是子节点和父节点的关系,二者不可以都选取,或者不可以都不选取,所以代码几乎都一样。

接下来看一道比前面难一点的题目

(http://acm.hdu.edu.cn/showproblem.php?pid=1561)

题意:有n个城堡形成一棵树,你只能攻打m个城堡,每个城堡里都有相应的宝物,要攻克这些城堡必须先攻克其他某一个特定的城堡,就是先攻克根,才可以往下攻击子树,问可以获得最大的宝物。

思路:这道题目和前面不同的是,有两个限制条件,一个是必学攻克父节点才能去攻打子节点,另一个是只能攻克m个城堡。这里可以看出树形dp和依赖背包是不是有一点差不多。其实解决依赖背包问题,就是用树形dp的思路,先解决子树的最优解,然后才能得到根的最优解。过程是从下往上的,而树的DFS就是先搜索到叶子节点,然后逐层往上,不断更新结点的最优值,最终保证根节点的值是最优的。状态转移方程见代码里

#include <iostream>
#include <string.h>
#include <algorithm>
#include <stdlib.h>
#include <math.h> using namespace std;
#define MAX 200
struct Node
{
int value;
int next;
}edge[MAX+5];
int tot;
int head[MAX+5];
int n,m;
int num[MAX+5];
int dp[MAX+5][MAX+5];
int vis[MAX+5];
void add(int x,int y)
{
edge[tot].value=y;
edge[tot].next=head[x];
head[x]=tot++;
}
void dfs(int root,int tag)
{
vis[root]=1;
dp[root][1]=num[root];
for(int i=head[root];i!=-1;i=edge[i].next)
{
int t=edge[i].value;
if(!vis[t])
{
dfs(t,tag-1);
}
for(int j=tag;j>=1;j--)
{
for(int k=1;k<j;k++ )
{
//状态转移方程
dp[root][j]=max(dp[root][j],dp[root][j-k]+dp[t][k]);
}
}
}
}
int main()
{
int a;
while(scanf("%d%d",&n,&m)!=EOF)
{
if(n==0&&m==0)
break;
tot=0;
memset(head,-1,sizeof(head));
memset(dp,0,sizeof(dp));
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;i++)
{
scanf("%d%d",&a,&num[i]);
add(a,i);
}
dfs(0,m+1);
printf("%d\n",dp[0][m+1]);
}
return 0;
}

代码看懂了也很好理解,接下来看这个问题的升级版(http://acm.hdu.edu.cn/showproblem.php?pid=1011)

题意:和上面一提差不多,变的是每个结点被攻克要求不同士兵数量

//
// main.cpp
// 树形DP2
//
// Created by 陈永康 on 16/1/3.
// Copyright (c) 2016年 陈永康. All rights reserved.
// #include <iostream>
#include <string.h>
#include <algorithm>
#include <math.h>
#include <stdlib.h> using namespace std;
int dp[105][105];//当前节点为i,小兵数为j得到的最大能量值
int n,m;
int bug[105];
int brain[105];
int head[105];
int tot;
int vis[105];
struct Node
{
int value;
int next;
}edge[105*2];
void add(int x,int y)
{
edge[tot].value=y;
edge[tot].next=head[x];
head[x]=tot++;
}
void dfs(int root,int tag)
{
vis[root]=1;
int term=(bug[root]+19)/20; dp[root][term]=brain[root];
for(int i=head[root];i!=-1;i=edge[i].next)
{
int u=edge[i].value;
if(!vis[u])
{
dfs(u,tag-term); for(int j=tag;j>=term;j--)
{
for(int k=1;j+k<=m;k++)
{
if(dp[u][k])
dp[root][j+k]=max(dp[root][j+k],dp[root][j]+dp[u][k]);
}
}
}
}
}
int main()
{
int x,y;
while(scanf("%d%d",&n,&m)!=EOF)
{
if(n==-1&&m==-1)
break;
memset(vis,0,sizeof(vis));
memset(head,-1,sizeof(head));
tot=0;
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++)
{
scanf("%d%d",&bug[i],&brain[i]);
}
for(int i=1;i<n;i++)
{
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
dfs(1,m);
if(m==0)
printf("0\n");
else
printf("%d\n",dp[1][m]);
}
}
值得注意的是还有另外一个ac代码,它和上面有一点不同
#include <iostream>
#include <string.h>
#include <algorithm>
#include <math.h>
#include <stdlib.h> using namespace std;
int dp[105][105];//当前节点为i,小兵数为j得到的最大能量值
int n,m;
int bug[105];
int brain[105];
int head[105];
int tot;
int vis[105];
struct Node
{
int value;
int next;
}edge[105*2];
void add(int x,int y)
{
edge[tot].value=y;
edge[tot].next=head[x];
head[x]=tot++;
}
void dfs(int root)
{
vis[root]=1;
int term=(bug[root]+19)/20;
for(int i=term;i<=m;i++)
dp[root][i]=brain[root];
for(int i=head[root];i!=-1;i=edge[i].next)
{
int u=edge[i].value;
if(!vis[u])
{
dfs(u); for(int j=m;j>=term;j--)
{
for(int k=1;j+k<=m;k++)
{
if(dp[u][k])
dp[root][j+k]=max(dp[root][j+k],dp[root][j]+dp[u][k]);
}
}
}
}
}
int main()
{
int x,y;
while(scanf("%d%d",&n,&m)!=EOF)
{
if(n==-1&&m==-1)
break;
memset(vis,0,sizeof(vis));
memset(head,-1,sizeof(head));
tot=0;
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++)
{
scanf("%d%d",&bug[i],&brain[i]);
}
for(int i=1;i<n;i++)
{
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
dfs(1);
if(m==0)
printf("0\n");
else
printf("%d\n",dp[1][m]);
}
}

这段代码并没有像前面那样,dfs的时候加上当前结点可以使用的小兵数量,似乎这样写下去并没有体现要攻打子节点必须攻克父节点这个限制条件啊,但这的确是对的。我的猜想是,这是两个不同的规划方式,第一个规划方式,你可以打一个dp二维数组的表,发现它是一个严格的从根节点往下,必须满足父节点被攻克,子节点才可以有最优值。而第二种方式,你可以发现,它的dp二维数组表,是以每一个结点为根节点得到这个结点的最优值。也就是说当我规划到这个节点,不用管它的父节点他就是根。所以回朔到根节点的时候,依然可以保证要攻打子节点必须攻克父节点这个限制条件,最终的值也是正确的。如果问题变成这样,你可以事先挑选某个结点作为根节点,往下去攻打。那么第二种解法就满足了

接下来,还是这个类型的题目但是又要更难一些,

(http://acm.hdu.edu.cn/showproblem.php?pid=4169)

题意:这个题目和前面的题目的区别就是,把要攻打子节点必须攻克父节点这个限制条件去掉了。加上一个,子节点和父节点不能共存。而且结点有1500000,所以你用dp数组表示结点的状态是肯定炸的。关键在于怎么设定dp数组,我感觉要不看博客accept这一题,是要对动态规划十分熟悉的。状态转移方程见代码,我把我的理解都写在注释里了

#include <iostream>
#include <string.h>
#include <algorithm>
#include <math.h>
#include <stdlib.h> using namespace std;
#define MAX 1500000
int v[MAX+5];
int head[MAX+5];
int dp[305];
int n;
int tot;
int m;
struct Node
{
int value;
int next;
}edge[MAX+5];
void add(int x,int y)
{
edge[tot].value=y;
edge[tot].next=head[x];
head[x]=tot++;
}
int dfs(int root)
{
int t=0;
int tag=0;
int cur[305];//这个数组是不断替换的
for(int i=0;i<=m;i++)
dp[i]=cur[i]=-1;
dp[0]=cur[0]=0;
for(int i=head[root];i!=-1;i=edge[i].next)
{
tag=1;
int k=edge[i].value;
//得到子树的状态数
int state=dfs(k);
for(int j=t;j>=0;j--)
{
for(int p=1;p<=state;p++)
{
if(p+j>m)
break;
cur[p+j]=max(cur[p+j],dp[p]+cur[j]);
}
}
t+=state;
}
if(!tag) t++;
cur[1]=max(cur[1],v[root]);
//把当前子树的状态保存到dp数组里,这里的dp数组表示当前这颗树的最优值,cur数组也是
for(int i=0;i<=m;i++)
dp[i]=cur[i];
return t;
}
int main()
{
int a;
int root;
while(scanf("%d%d",&n,&m)!=EOF)
{
memset(head,-1,sizeof(head));
tot=0;
for(int i=1;i<=n;i++)
{
scanf("%d%d",&a,&v[i]);
if(a==0)
root=i;
add(a,i);
}
dfs(root);
if(dp[m]==-1)
printf("impossible\n");
else
printf("%d\n",dp[m]); }
return 0;
}

这道题目定义了两个状态数组,cur[],dp[]。dp[]里面保存的是最终的答案,cur[]是一个临时的数组表示子树的状态。

看另一道不同风格的题目吧

(http://acm.hdu.edu.cn/showproblem.php?pid=2412)

这道题目和前面一道非常相似,但是有一点不同,它要询问这个最优值是不是只有一种组合方式。这道题目的解决方法是,再开一个状态数组,表示当前的最优值是不是唯一,如果能想到这里,问题就好解决了

#include <iostream>
#include <string.h>
#include <math.h>
#include <stdlib.h>
#include <algorithm>
#include <map>
#include <string> using namespace std;
#define MAX 200
map<string,int>m;
int n;
string a,b;
int tot;
int head[MAX+5];
int dp[MAX+5][2];
int dup[MAX+5][2];
bool tag;
struct Node
{
int value;
int next;
}edge[MAX*2+5];
void add(int x,int y)
{
edge[tot].value=y;
edge[tot].next=head[x];
head[x]=tot++;
}
void dfs(int root)
{
dp[root][1]=1;
dp[root][0]=0;
dup[root][1]=1;
dup[root][0]=1;
for(int i=head[root];i!=-1;i=edge[i].next)
{
int k=edge[i].value;
dfs(k);
dp[root][1]+=dp[k][0];
dp[root][0]+=max(dp[k][0],dp[k][1]);
if(dp[k][1]<dp[k][0]&&dup[k][0]==0)
dup[root][0]=0;
else if(dp[k][1]>dp[k][0]&&dup[k][1]==0)
dup[root][0]=0;
else if(dp[k][1]==dp[k][0])
dup[root][0]=0;
if(dup[k][0]==0)
dup[root][1]=0; }
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
if(n==0)
break;
memset(head,-1,sizeof(head));
m.clear();
cin>>a;
m[a]=1;
int cnt=1;
tot=0;
for(int i=1;i<n;i++)
{
cin>>a>>b;
if(!m.count(a))
m[a]=++cnt;
if(!m.count(b))
m[b]=++cnt;
add(m[b],m[a]);
}
memset(dp,0,sizeof(dp));
memset(dup,0,sizeof(dup));
dfs(1);
if(dp[1][1]>dp[1][0]&&dup[1][1]==1)
printf("%d Yes\n",dp[1][1]);
else if(dp[1][0]>dp[1][1]&&dup[1][0]==1)
printf("%d Yes\n",dp[1][0]);
else
printf("%d No\n",max(dp[1][1],dp[1][0]));
}
return 0; }

前面说的都是在节点的值上面进行动态规划,下面看如果要在边上吗进行规划呢?

HDU-3452 Bonsai

题意就是给你一棵树,每个边都有权值,问你剪去那些边,可以让叶子节点不能达到根节点,且剪去边的权值和最小

思路:为了达到让叶子节点不能达到根节点的目的,你有两种选择要么剪去根节点和下一个子节点的边,要么不剪这条边,直接到子树里面取剪。状态转移方程也就清晰了,见代码里

#include <iostream>
#include <string.h>
#include <algorithm>
#include <math.h>
#include <stdlib.h> using namespace std;
#define MAX 1000
struct Node
{
int value;
int next;
int weight;
}edge[MAX*2+5];
int tot;
int n,r;
int a,b,c;
int head[MAX+5];
int vis[MAX+5];
int dp[MAX+5];
void add(int x,int y,int w)
{
edge[tot].weight=w;
edge[tot].value=y;
edge[tot].next=head[x];
head[x]=tot++;
}
void dfs(int root)
{ vis[root]=1;
int tag=0;
int sum;
for(int i=head[root];i!=-1;i=edge[i].next)
{
int k=edge[i].value;
sum=edge[i].weight;
if(!vis[k])
{
tag=1;
dfs(k);
dp[root]+=min(sum,dp[k]);
} }
if(tag==0)
dp[root]=sum;
}
int main()
{ while(scanf("%d%d",&n,&r)!=EOF)
{ if(n==0&&r==0)
break;
tot=0;
memset(head,-1,sizeof(head));
memset(dp,0,sizeof(dp));
memset(vis,0,sizeof(vis));
for(int i=0;i<n-1;i++)
{
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
add(b,a,c);
}
if(n==1)
printf("0\n");
else
{
dfs(r);
printf("%d\n",dp[r]);
} }
return 0;
}

再看一道这道题目的升级版,题意一样,不同的就是问你的是最小限制,就是你选取剪去的边里面权值最大的。这道题目的思路就是树形dp加二分,状态转移方程稍微变一下,加上一个限制。二分其实就是枚举这个限制,二分更快一点而已。

题目要问的是最小的最大限制,必然二分答案。

HDU-3586 Information Disturbing

#include <iostream>
#include <string.h>
#include <algorithm>
#include <stdlib.h>
#include <math.h> using namespace std;
#define MAX 1000
int dp[MAX+5];
int head[MAX+5];
int vis[MAX+5];
int n,m;
int tot;
int maxin;
struct Node
{
int value;
int weight;
int next;
}edge[MAX*2+5];
void add(int x,int y,int weight)
{
edge[tot].weight=weight;
edge[tot].value=y;
edge[tot].next=head[x];
head[x]=tot++;
}
void dfs(int root,int limit)
{ vis[root]=1;
bool tag=false;
int sum=0;
for(int i=head[root];i!=-1;i=edge[i].next)
{ int k=edge[i].value;
sum=edge[i].weight;
if(!vis[k])
{
tag=true;
dfs(k,limit);
if(sum>limit)
dp[root]+=dp[k];
else
dp[root]+=min(dp[k],sum); }
}
if(!tag)
dp[root]=1000010;
}
int main()
{
int x,y,z;
while(scanf("%d%d",&n,&m)!=EOF)
{
if(n==0&&m==0)
break;
memset(head,-1,sizeof(head));
tot=0;maxin=0;
for(int i=1;i<n;i++)
{
scanf("%d%d%d",&x,&y,&z);
maxin=max(maxin,z);
add(x,y,z);
add(y,x,z);
}
int l=1;int r=maxin;int ans=-1;
while(l<=r)
{
int mid=(l+r)/2;
memset(vis,0,sizeof(vis));
memset(dp,0,sizeof(dp));
dfs(1,mid);
if(dp[1]<=m)
{
ans=mid;
r=mid-1;
}
else
l=mid+1;
}
printf("%d\n",ans); }
return 0;
}

树形DP总结,持续更新的更多相关文章

  1. 【转】【DP_树形DP专辑】【9月9最新更新】【from zeroclock's blog】

    树,一种十分优美的数据结构,因为它本身就具有的递归性,所以它和子树见能相互传递很多信息,还因为它作为被限制的图在上面可进行的操作更多,所以各种用于不同地方的树都出现了,二叉树.三叉树.静态搜索树.AV ...

  2. 状压dp(状态压缩&&dp结合)学习笔记(持续更新)

    嗯,作为一只蒟蒻,今天再次学习了状压dp(学习借鉴的博客) 但是,依旧懵逼·································· 这篇学习笔记是我个人对于状压dp的理解,如果有什么不对的 ...

  3. DP刷题记录(持续更新)

    DP刷题记录 (本文例题目前大多数都选自算法竞赛进阶指南) TYVJ1071 求两个序列的最长公共上升子序列 设\(f_{i,j}\)表示a中的\(1-i\)与b中色\(1-j\)匹配时所能构成的以\ ...

  4. 理解DP(持续更新)

    理解DP author: thy from buaa 初见 dynamic programming(可以理解为动态刷表法 其实这里的programming并不是编程而是规划.设计表格的意思) 关于动态 ...

  5. 提升——树形DP

    这里讲提高一点的内容,所以没有树形DP基础的,先看一下基础部分: 浅说——树形DP 闲言不表,看第一题. 这道题是典型的树上最长链问题.(就是一个模板题) 给定一棵树,树上共有N个节点(N<=5 ...

  6. 【BZOJ-3572】世界树 虚树 + 树形DP

    3572: [Hnoi2014]世界树 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 1084  Solved: 611[Submit][Status ...

  7. 【BZOJ-3631】松鼠的新家 树形DP?+ 倍增LCA + 打标记

    3631: [JLOI2014]松鼠的新家 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 1231  Solved: 620[Submit][Stat ...

  8. 洛谷P2014 选课 (树形dp)

    10月1日更新.题目:在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习,在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习.现在有N门功课,每门课有个学分 ...

  9. POJ3162 Walking Race(树形DP+尺取法+单调队列)

    题目大概是给一棵n个结点边带权的树,记结点i到其他结点最远距离为d[i],问d数组构成的这个序列中满足其中最大值与最小值的差不超过m的连续子序列最长是多长. 各个结点到其他结点的最远距离可以用树形DP ...

随机推荐

  1. web页面 验证码 生成

    web页面 验证码 生成 kaptcha 是一个非常实用的验证码生成工具.有了它,你可以生成各种样式的验证码,因为它是可配置的.kaptcha工作的原理是调用 com.google.code.kapt ...

  2. ASP.NET MVC 4 (四) 控制器扩展

    MVC的标准流程是请求传递给控制器,由控制器action方法操作数据模型,最后交由视图渲染输出,这里忽略了两个细节,就是MVC是如何创建相应控制器实例,又是如何调用控制器action方法的,这就必须讲 ...

  3. python中安装dlib和cv2

    这两个模块是很容易出问题的模块,以下的解决办法都是从网上收集而来. 安装dlib: pypi.python.org/pypi/dlib/19.6.0 下载 dlib-19.6.0-cp36-cp36m ...

  4. Weblogic集群部署

    有些事情不去尝试,注定是失败,如果预知90%的失败仍然去尝试了,那也会从中学到很多,何况仅靠那10%的可能性也会成功 weblogic安装后 1.打开Configuration Wizard 2.创建 ...

  5. 【Python 爬虫系列】从某网站下载小说《鬼吹灯》,正则解析html

    import re import urllib.request import urllib.parse import urllib.error as err import time # 下载 seed ...

  6. Lottie 动画库

    强大的动画库,且同时支持 android.ios.react native 和 web http://airbnb.io/lottie/

  7. python卸载或者安装时提示There is a problem with this Windows Installer package.A program required for this install to complete could not be run. Contact your support personnel or package vendor

    1.卸载时报这个错,先进行下修复,再执行卸载: 2.安装时报这个错,安装的过程中,没有取得管理员的权限. Msi格式的文件,点右键后,也没有“以管理员身份运行”的菜单项,那怎么办呢?你可以点“开始”菜 ...

  8. Spring系列之IOC容器

    一.概述 IOC容器就是具有依赖注入功能的容器,IOC容器负责实例化.定位.配置应用程序中的对象及建立这些对象之间的依赖.应用程序无需直接在代码中new 相关的对象,应用程序由IOC容器进行组装.在S ...

  9. 【linux系列】centos7配置桥接模式静态IP

    一.设置桥接模式 VMware->Edit->Virtual Network Edit 二.查看物理机的ip地址 三.根据物理机的ip地址,设置linux虚拟机的ip地址 四.网络重启 五 ...

  10. win10下网狐荣耀手机端android app编译

    基于荣耀版(2017.5.21)12 款游戏..7z这款游戏,网上有下载的 1.解压后进入 cd shoujiduan 2.将client/base复制到client/ciphercode/下,也就是 ...