一、
树形dp顾名思义是是建立在树上的动态规划。所以实现树形dp的第一步是建树。下面是两种比较常见的建树方法。
1、用vector来建树,优点是比较简单直观,方便调试,缺点是运行时间较长,有些题目会TLE(如 POJ - 3107 Godfather)。
vector e[maxn];
for(int i=0;i
2、用邻接表来建树,优点是运行时间较短,缺点是较为不直观,调试不方便(大神别打我..)。
以POJ - 3107 Godfather这题的样例为例,题目的树是一颗无向树,所以我们要把每条边的端点正着加进去,然后反着再加进去(例如下图左上角的1 2,我们先add(1,2),然后再add(2,1)),把所有边加进数组后,head数组与edge数组如图:
如果是有向树,则将每条边按方向加进去即可(如下图左上角的1 2,我们只要add(1,2)),把所有边加进数组后,head数组与edge数组如图:
struct edge
{
int v,next;
} edge[maxn<<1];
int head[maxn],cnt;
void init()
{
memset(head,-1,sizeof(head));
cnt=0;
}
void add(int u,int v)
{
edge[cnt].v=v;
edge[cnt].next=head[u];
head[u]=cnt++;
}
for(int i=1; i
二、树形dp的状态一般与节点有关(废话..),它的转移方程一般有两个方向:根节点->叶子节点、叶子节点->根节点
其中叶子节点->根节点一般是在dfs的回溯时更新父亲节点的值(如POJ - 2342 Anniversary party),而根节点->叶子节点则比较复杂,先dfs用子节点处理用父节点,再一次dfs用父节点处理子节点(如HDU - 2196 Computer)。
三、具体题目
1、点击打开链接
Time Limit: 1000MS | Memory Limit: 65536K | |
Total Submissions: 8906 | Accepted: 5122 |
Description
Input
Output
Sample Input
7 1 1 1 1 1 1 1 1 3 2 3 6 4 7 4 4 5 3 5 0 0
Sample Output
5
题意:每个节点都有一个权值,问选择哪些点使得权值和最大且被选择的点之间没有父子关系,输出最大权值和。
分析:树形dp的裸题,对于每个节点我们可以选择选or不选,所以我们用dp[i][0/1]表示第i个节点选or不选的最大权值和。因为每个节点选or不选影响的只有它的儿子节点或父亲节点,所以,我们可以写出状态转移方程为:
dp[u][1]=sum(dp[v][0])
dp[u][0]=sum(max(dp[v][1],dp[v][0])) (v表示u的儿子节点)
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn=6e3+11;
const int inf=0x7fffffff;
int n;
int p[maxn],dp[maxn][2];
vector e[maxn];
void dfs(int u)
{
if(e[u].size()==0) return;
int l=e[u].size();
for(int i=0;i
Time Limit: 2000MS | Memory Limit: 65536K | |
Total Submissions: 7244 | Accepted: 2547 |
Description
Last years Chicago was full of gangster fights and strange murders. The chief of the police got really tired of all these crimes, and decided to arrest the mafia leaders.
Unfortunately, the structure of Chicago mafia is rather complicated. There are n persons known to be related to mafia. The police have traced their activity for some time, and know that some of them are communicating with each other. Based on the data collected, the chief of the police suggests that the mafia hierarchy can be represented as a tree. The head of the mafia, Godfather, is the root of the tree, and if some person is represented by a node in the tree, its direct subordinates are represented by the children of that node. For the purpose of conspiracy the gangsters only communicate with their direct subordinates and their direct master.
Unfortunately, though the police know gangsters’ communications, they do not know who is a master in any pair of communicating persons. Thus they only have an undirected tree of communications, and do not know who Godfather is.
Based on the idea that Godfather wants to have the most possible control over mafia, the chief of the police has made a suggestion that Godfather is such a person that after deleting it from the communications tree the size of the largest remaining connected component is as small as possible. Help the police to find all potential Godfathers and they will arrest them.
Input
The first line of the input file contains n — the number of persons suspected to belong to mafia (2 ≤ n ≤ 50 000). Let them be numbered from 1 to n.
The following n − 1 lines contain two integer numbers each. The pair ai, bi means that the gangster ai has communicated with the gangster bi. It is guaranteed that the gangsters’ communications form a tree.
Output
Print the numbers of all persons that are suspected to be Godfather. The numbers must be printed in the increasing order, separated by spaces.
Sample Input
6 1 2 2 3 2 5 3 4 3 6
Sample Output
2 3题意:输出无向树的所有重心。 树的重心:树的重心也叫树的质心。对于一棵树n个节点的无根树,找到一个点,使得把树变成以该点为根的有根树时,最大子树的结点树最小。换句话说,删除这个点后最大连通块(一定是树)的结点数最小。
分析:我们要记录以每个节点为根所生成的树的每个子树上的节点数。
观察上图我们可以知道对于某个节点的其中一个子树的节点数x等于所有节点数n减该节点其他子树的总节点数。所以,dp[u]表示第u个节点的子树的最大节点数,我们写出转移方程:dp[u]=max(dp[u],sum[v],n-sum[u]) sum[u]表示第u个节点所有子树的总节点数。
这题的状态和转移方程不难找,但是这题用vector来建树会TLE,所以要用邻接表来建树。
#include
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn=5e4+11;
const int inf=1e9+7;
const int mod=1e9+7;
struct edge
{
int v,next;
}edge[maxn<<1];
int head[maxn],sum[maxn],ans[maxn];
int cnt,n,minl,num;
void add(int u,int v)
{
edge[cnt].v=v;
edge[cnt].next=head[u];
head[u]=cnt++;
}
void dfs(int u,int fa)
{
int maxl=0,tot=0;
sum[u]=1;
for(int i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].v;
if(v==fa) continue;
dfs(v,u);
sum[u]+=sum[v];
maxl=max(sum[v],maxl);
tot+=sum[v];
}
maxl=max(maxl,n-tot-1);
if(minl>maxl)
{
minl=maxl;
num=0;
ans[num++]=u;
}
else if(minl==maxl)
{
ans[num++]=u;
}
}
void work()
{
memset(head,-1,sizeof(head));
cnt=0; minl=inf;
for(int i=1;i
题意: 求树上每个点能到达的最远距离。
分析:在这题中,我们发现父节点无法通过子节点得到能到达该父节点的最远距离,即我们无法从叶子节点向根节点进行dp,所以我们现在思考从根节点向叶子节点进行dp。
通过观察图,我们可以知道对于一个节点来说,离它最远的节点要么是该节点的子节点,要么是该节点的父节点的其中一个子节点(且这个子节点是离该父节点最远的节点或次远的节点)。所以,我们先dfs(1)遍历整棵树,记录以节点u为根的子树中离u最远的距离和次远的距离(注意此时我们并没有求出以u为根的新树中离u最远的距离和次远的距离,此时树的根仍为1)。然后,我们再dfs(1,-1)遍历整棵树,更新以u为根的新树中离u最远的距离和次远的距离,并求出u能到达的最远距离。
#include
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn=1e4+11;
const int inf=1e9+7;
const int mod=1e9+7;
typedef struct node
{
int v,l;
} node;
vector e[maxn];
int dp[maxn][3];
int n;
void dfs1(int u)
{
int len=e[u].size();
if(len==0) return;
int a=0,b=0;
for(int i=0; idp[u][0])
{
dp[u][2]=tmp;
dp[u][1]=max(dp[u][1],dp[u][0]);
dp[u][0]=tmp;
}
else
{
dp[u][2]=dp[u][0];
dp[u][1]=max(dp[u][1],tmp);
}
len=e[u].size();
for(int i=0; i
4、点击打开链接
以上都是树形dp的经典入门题,下面的是个人排位的一道题,是第4场的B题《The more, The Better》
分析:对于每个节点我们都有选or不选两个选择,所以这是个背包问题,且题目限制,对于节点v,如果我们选了v,则v的父节点u一定被选了。类似于背包问题,我们定义状态为dp[u][m]表示选取节点u及其子树(不包括节点u)上拿m个城市的最大价值。所以转移方程为:
for(int j=m;j>=0;j--)
{
for(int k=0;k<=j;k++)
{
dp[u][j]=max(dp[u][j],dp[u][j-k]+dp[v][k]);
}
}
#include
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn=211;
const int inf=1e9+7;
const int mod=1e9+7;
int n,m;
vector e[maxn];
int dp[maxn][maxn];
int p[maxn];
void dfs(int u)
{
int len=e[u].size();
for(int i=0;i=0;j--)
{
for(int k=0;k<=j;k++)
{
dp[u][j]=max(dp[u][j],dp[u][j-k]+dp[v][k]);
}
}
}
// printf("%d:",u);
// for(int i=0;i<=m;i++) cout<=0;i--)
{
dp[u][i+1]=dp[u][i]+p[u];
}
}
void work()
{
for(int i=0;i<=n;i++)
e[i].clear();
memset(dp,0,sizeof(dp));
memset(p,0,sizeof(p));
for(int i=1;i<=n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
e[x].push_back(i);
p[i]=y;
}
dfs(0);
// for(int i=0;i<=n;i++)
// {
// for(int j=0;j<=m;j++)
// cout<
四、感想
学了树形dp后,对树的理解加深了,掌握了建树的基础知识。同时也对dp进一步加深理解。dp一般考虑当前状态与下一状态的转移,而树形dp则状态一般与节点有关,一般考虑父节点与子节点的状态转移,有时要从父节点到子节点,有时要子节点到父节点,但总体来说大概就是这两个方向。