树形DP题目分类
1、树的重心
找到一个点,其所有的子树中最大的子树节点数最少,那么这个点就是这棵树的重心。删去重心后,生成的多棵树尽可能平衡。
对于一棵n个节点的无根树,找到一个点,使得树变成以该点为根的有根树时,最大子树的结点数最小。换句话说,删除这个点后最大连通块(一定是树)的结点数最小。
poj 1655 Balancing Act
题意:求树的重心以及删除重心后最大子树的节点数,如果有多个重心,只输出编号最小的点。
从1开始搜索,把它作为整个树的根(哪个点作为树根对答案是没有影响的,不妨设树根编号为1,从1开始搜索)。dp[i]表示以 i 为根的最大子树的大小(包括 i 自身)。
注意dfs函数中多次递归,dfs函数内的一些变量不要设成全局变量,否则会错。
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int N=2e4+10;
int n,t,x,y,cnt,ans1,ans2,dp[N],head[N];
struct edge
{
int to,next;
}e[N<<1];//无向树 边*2
void add(int x,int y)//链式前向星存边
{
e[cnt].to=y;
e[cnt].next=head[x];
head[x]=cnt++;
}
void dfs(int u,int father)//u为当前节点,father为u的父节点
{
dp[u]=1;//u自身就是一个子树,大小至少为1
int mx1=0,mx2=0;//mx1,mx2不能写成全局变量!
for(int i=head[u];i!=-1;i=e[i].next)
{
int v=e[i].to;//v为u的子节点(v不能写成全局变量!)
if(v==father)continue;//只找u的子节点,跳过父节点
dfs(v,u);//向下方递归
dp[u]=dp[u]+dp[v];//递归返回上层,向上方回溯,记录dp值
mx1=max(mx1,dp[v]);//找以v为根的最大子树,或者说是u下方(不包括u)的最大子树
}
mx2=max(mx1,n-dp[u]);//u的上方还有子树,它的大小是n-dp[u],可能它会比u下方的最大子树要大
if(mx2<ans2)
{
ans1=u;
ans2=mx2;
}
}
int main()
{
ios::sync_with_stdio(false);
cin>>t;
while(t--)
{
cin>>n;
memset(head,-1,sizeof(head));
cnt=0;ans2=0x3f3f3f3f;
for(int i=1;i<n;i++)
{
cin>>x>>y;
add(x,y);
add(y,x);
}
dfs(1,0);//从1开始搜索,设1的父节点为0
printf("%d %d\n",ans1,ans2);
}
return 0;
}
poj 3107 Godfather
题意:求树的重心,如果有多个重心,按升序全部输出。
把上题代码稍加改动,用 ans1[i] 表示以 i 为根的最大子树的节点数(不包括根本身),ans2表示以1~n为根的所有的最大子树的节点数(不包括根本身)。
还有就是这题在poj的玄学评测下,如果算法正确,但是输入写的是ios::sync_with_stdio(false)优化的cin,会超时(2000ms+),改成scanf输入就AC了…我寻思着也就poj的玄学评测姬能做到cin输入优化还超时,这问题在其他OJ我是真没遇到过…
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=5e4+10;
int n,t,x,y,cnt,ans1[N],ans2,dp[N],head[N];
struct edge
{
int to,next;
}e[N<<1];
void add(int x,int y)
{
e[cnt].to=y;
e[cnt].next=head[x];
head[x]=cnt++;
}
void dfs(int u,int father)
{
dp[u]=1;
int mx=0;//mx不能写成全局变量!
for(int i=head[u];i!=-1;i=e[i].next)
{
int v=e[i].to;//v不能写成全局变量!
if(v==father)continue;
dfs(v,u);
dp[u]+=dp[v];
mx=max(mx,dp[v]);
}
mx=max(mx,n-dp[u]);
if(mx<=ans2)
{
ans1[u]=mx;
ans2=mx;//ans1[u]表示以u为根的最大子树的节点数(不包括根本身),ans2是以1~n为根的所有的最大子树的节点数(不包括根本身)
}
}
int main()
{
scanf("%d",&n);
memset(head,-1,sizeof(head));
ans2=0x3f3f3f3f;
for(int i=1;i<n;i++)
{
scanf("%d %d",&x,&y);
add(x,y);
add(y,x);
}
dfs(1,0);
for(int i=1;i<=n;i++)
if(ans1[i]==ans2)printf("%d ",i);
return 0;
}
hdu 1520 Anniversary party
题意:在树中,每个节点有一个权值,相邻的父节点和子节点中最多只能选择一个,问如何选择使得权值和最大。
还有就是注意本题是多组输入(单组就WA了)
#include <bits/stdc++.h>
using namespace std;
const int N=6010;
int n,x,y,s,cnt,val[N],vis[N],head[N],dp[N][2];//dp[i][0]表示不取i点时的最优解,dp[i][1]表示取i点时的最优解
struct edge
{
int to,next;
}e[N<<1];
void add(int x,int y)
{
e[cnt].to=y;
e[cnt].next=head[x];
head[x]=cnt++;
}
void dfs(int u,int father)
{
dp[u][0]=0;//不取u点,dp值为0
dp[u][1]=val[u];//取u点,dp值为val[u]
for(int i=head[u];i!=-1;i=e[i].next)
{
int v=e[i].to;
if(v==father)continue;
dfs(v,u);
dp[u][0]+=max(dp[v][1],dp[v][0]);//不取u点时,v点可取(dp[v][1])可不取(dp[v][0]),dp[u][0]加上两者的最大值
dp[u][1]+=dp[v][0];//取u点时,v点不可取,dp[u][1]加上dp[v][0]
}
}
int main()
{
ios::sync_with_stdio(false);
while(cin>>n)
{
cnt=0;
memset(vis,0,sizeof(vis));
memset(head,-1,sizeof(head));
for(int i=1;i<=n;i++)
cin>>val[i];//记录点的权值
while(cin>>x>>y&&!(x==0&&y==0))//x是y的子节点
{
add(x,y);
add(y,x);
vis[x]=1;//标记作为子节点的x
}
for(int i=1;i<=n;i++)
{
if(vis[i]==0)
{s=i;break;}//找树根s,它没有被标记为其他任何点的子节点
}
dfs(s,0);//从树根s开始向下搜索
printf("%d\n",max(dp[s][0],dp[s][1]));
}
return 0;
}
nefu 248 聚会的快乐
和上题差不多。注意一下用map将string映射到int的过程。
#include <bits/stdc++.h>
using namespace std;
const int N=110;
string sx,sy;
map<string,int>vis;
map<string,int>id;
int n,x,y,z,s,num,cnt,is_son[N],val[N],head[N],dp[N][2];
struct edge
{
int to,next;
}e[N<<1];
void add(int x,int y)
{
e[cnt].to=y;
e[cnt].next=head[x];
head[x]=cnt++;
}
void dfs(int u,int father)
{
dp[u][0]=0;
dp[u][1]=val[u];
for(int i=head[u];i!=-1;i=e[i].next)
{
int v=e[i].to;
if(v==father)continue;
dfs(v,u);
dp[u][0]+=max(dp[v][0],dp[v][1]);
dp[u][1]+=dp[v][0];
}
}
int main()
{
ios::sync_with_stdio(false);
while(cin>>n)
{
memset(head,-1,sizeof(head));
memset(is_son,0,sizeof(is_son));
memset(val,0,sizeof(val));
num=0;cnt=0;
vis.clear();
for(int i=1;i<=n;i++)
{
cin>>sx>>z>>sy;
vis[sx]++;//vis记录字符串出现的次数
vis[sy]++;
if(vis[sx]==1)id[sx]=++num;//num为现在的编号,id[]记录字符串对应的编号
x=id[sx];
if(vis[sy]==1)id[sy]=++num;
y=id[sy];
val[x]=z;
is_son[x]=1;
add(x,y);
add(y,x);
}
for(int i=1;i<=num;i++)
{
if(is_son[i]==0)
{s=i;break;}//s为树根
}
dfs(s,0);
printf("%d\n",max(dp[s][0],dp[s][1]));
}
return 0;
}
hdu 1561 The more, The Better
#include <bits/stdc++.h>
using namespace std;
const int N=210;
int n,m,y,z,cnt,val[N],head[N],dp[N][N];
struct edge
{
int to,next;
}e[N<<1];
void add(int x,int y)
{
e[cnt].to=y;
e[cnt].next=head[x];
head[x]=cnt++;
}
void dfs(int u,int father)
{
dp[u][1]=val[u];
for(int i=head[u];i!=-1;i=e[i].next)
{
int v=e[i].to;
if(v==father)continue;
dfs(v,u);
for(int j=m;j>1;j--)
for(int k=0;k<j;k++)
dp[u][j]=max(dp[u][j],dp[u][j-k]+dp[v][k]);
}
}
int main()
{
ios::sync_with_stdio(false);
while(cin>>n>>m&&!(n==0&&m==0))
{
cnt=0;
memset(dp,0,sizeof(dp));
memset(head,-1,sizeof(head));
for(int i=1;i<=n;i++)
{
cin>>y>>z;
add(i,y);
add(y,i);
val[i]=z;
}
m++;
dfs(0,-1);
printf("%d\n",dp[0][m]);
}
return 0;
}
hdu 2196 Computer
#include <bits/stdc++.h>
using namespace std;
const int N=1e4+10;
int n,y,z,cnt,dp[N][3],head[N];
struct edge
{
int w,to,next;
}e[N<<1];
void add(int x,int y,int z)
{
e[cnt].to=y;
e[cnt].w=z;
e[cnt].next=head[x];
head[x]=cnt++;
}
void dfs1(int u,int father)
{
int one=0,two=0;
for(int i=head[u];i!=-1;i=e[i].next)
{
int v=e[i].to;
if(v==father)continue;
dfs1(v,u);
int tmp=dp[v][0]+e[i].w;
if(tmp>=one)
{
two=one;
one=tmp;
}
else if(tmp>two&&tmp<one)
two=tmp;
}
dp[u][0]=one;
dp[u][1]=two;
}
void dfs2(int u,int father)
{
for(int i=head[u];i!=-1;i=e[i].next)
{
int v=e[i].to;
if(v==father)continue;
if(dp[v][0]+e[i].w==dp[u][0])//u的最长距离在v及其下方
dp[v][2]=max(dp[u][2],dp[u][1])+e[i].w;
else//u的最长距离不在v及其下方
dp[v][2]=max(dp[u][2],dp[u][0])+e[i].w;
dfs2(v,u);
}
}
int main()
{
ios::sync_with_stdio(false);
while(cin>>n)
{
cnt=0;
memset(head,-1,sizeof(head));
for(int i=2;i<=n;i++)
{
cin>>y>>z;
add(i,y,z);
add(y,i,z);
}
dfs1(1,0);
dfs2(1,0);
for(int i=1;i<=n;i++)
printf("%d\n",max(dp[i][0],dp[i][2]));
}
return 0;
}