<!-- TOC -->
- 20200224树形dp
- 1. 一,回顾:
- 2. 二,从求树的直径出发,思考树形dp的一种方式
- 2.0.1. 湫湫系列故事——设计风景线 HDU - 4514
- 2.0.1.1. 题意
- 2.0.1.2. 题目解析
- 2.0.1.3. 算法解析
- 2.0.1.4. 具体代码
- 2.0.2. Book of Evil CodeForces - 337D
- 2.0.2.1. 题目大意
- 2.0.2.2. 题目解析
- 2.0.2.3. 算法解析
- 2.0.2.4. 具体代码
- 2.0.1. 湫湫系列故事——设计风景线 HDU - 4514
<!-- /TOC --> 20200224树形dp
1. 一,回顾:
之前关于树形dp的文章:
- 20200221_树形dp
在这篇文章里主要介绍了:最小生成树算法+将树在任意边划分为两个集合,计算两个集合之间的距离. 其中最小生成树算法,包括Kruskal和prim算法。
前者时间复杂度:O(mlogm);后者时间复杂度:O(n^2);
计算两个集合之间的距离:枚举每个点作为根节点,然后对每个点进行dfs,将根节点的信息,传递给它的子节点,这时子节点就可以代表它的根节点,进而作为集合的代表;
2. 二,从求树的直径出发,思考树形dp的一种方式
先来看一道例题,回顾一下求树的直径的方式:
2.0.1. 湫湫系列故事——设计风景线 HDU - 4514
题目链接
2.0.1.1. 题意
判断所给的图是否是存在环;如果是,输出YES;没有求出所给图的最长的路径;
2.0.1.2. 题目解析
- 并查集判断是否有环
- 最长的路径==树的直径
注意:题目中没有明确说明这是棵树,如果没有环,还可能是森林,即多个联通图,要注意;
2.0.1.3. 算法解析
用dfs求树的直径时,我们维护dp[i][2]数组;
dp[i][0],表示i的子节点到达i点的最长距离;
dp[i][1], 表示i的子节点到达i点的次长距离;这样计算每个节点的dp[i][0]+dp[i][1]; 取最大值,就可以得到树的直径关于这个性质可行性的验证:可以自己画一棵树,标出树的直径,然后对于直径上的每一点,验证:dp[i][0]+dp[i][1]
2.0.1.4. 具体代码
/*
* @Author: cy
* @Date: 2020-02-24 11:02:29
* @LastEditors: cy
* @LastEditTime: 2020-02-24 15:31:12
* @FilePath: 2_组队训练e:20201_code1_个人dp树形dp5.cpp
* @Problem: 湫湫系列故事——设计风景线 HDU - 4514
* @Link: https://vjudge.net/problem/HDU-4514
*/
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int num=1e5+10;
const int nue=2*1e6+10;
struct node{
int v,w,next;
}e[nue];
int head[num],cnt;
int dp[num][2],pre[num],n,m;
bool vs[num];
void int_i(void)
{
memset(head,-1,sizeof(head));
cnt=0;
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++) pre[i]=i;
memset(vs,0,sizeof(vs));
}
inline void addedge(int u,int v,int w)
{
e[++cnt].v=v;e[cnt].w=w;e[cnt].next=head[u];head[u]=cnt;
}
int fa(int x)
{
return (x==pre[x])?pre[x]:(pre[x]=fa(pre[x]));
}
int merge(int x,int y)
{
int tx=fa(x),ty=fa(y);
if(tx!=ty) {pre[tx]=ty;return 0;}
return 1;//出现环
}
void dfs(int u,int f)
{
vs[u]=1;
for(int i=head[u];i!=-1;i=e[i].next)
{
int v=e[i].v,w=e[i].w;
if(v==f) continue;
dfs(v,u);
if(dp[v][0]+w>=dp[u][0]){dp[u][1]=dp[u][0];dp[u][0]=dp[v][0]+w;}
else if(dp[v][0]+w>dp[u][1]) dp[u][1]=dp[v][0]+w;
}return ;
}
int main()
{
int u,v,w,f,i,ans;
while(scanf("%d%d",&n,&m)!=EOF)
{
int_i();
f=0;
for(i=1;i<=m;i++)
{
scanf("%d%d%d",&u,&v,&w);
if(merge(u,v)) f=1;//判环
addedge(u,v,w);addedge(v,u,w);
}
if(f) {printf("YESn");continue;}
for(i=1;i<=n;i++) if(vs[i]==0) dfs(i,-1);
ans=0;
for(i=1;i<=n;i++) ans=max(ans,dp[i][1]+dp[i][0]);
printf("%dn",ans);
}
return 0;
}
2.0.2. Book of Evil CodeForces - 337D
题目链接
2.0.2.1. 题目大意
在一棵树中,有若干点被一本神奇的魔法书破坏,该魔法书有一定的破坏范围d;
求出在树中,这本魔法书可以存在哪些节点中;或者没有符合的点;
2.0.2.2. 题目解析
在确定了树的根节点之后:对于树上的每个节点,以它为分割点,包括两部分:
它的子树部分,以及它的非子树部分(它的父节点以及它的父节点的其它子树以及它的父节点的父节点); 而我们要求的就是该节点到这两部分的最长距离;
1)针对它的子树:我们可以依靠树的求直径的方法,去求该节点在子树到被破坏的点的最长路径和次长路径
2)第二步:将父节点的信息转移到子节点上;
注意:这道题和求树的直径还有不同之处,树的直径是到根节点的距离,而这道题时到被破坏的节点的值。
2.0.2.3. 算法解析
1)求最长路径和次长路径,就是按照树的直径的dfs做,但要注意一点:如果子节点的子树中,不包含被
破坏的点,那么这个子节点就不能去更新父节点;
2)父节点的信息向子节点更新时:要注意: a. 父节点的最长路径是否经过子节点 b. 父节点的信息是否有效
2.0.2.4. 具体代码
/*
* @Author: cy
* @Date: 2020-02-23 11:41:35
* @LastEditors: cy
* @LastEditTime: 2020-02-24 22:24:04
* @FilePath: 2_组队训练e:20201_code1_个人dp树形dp4.cpp
* @Problem: Book of Evil CodeForces - 337D
* @Link: https://vjudge.net/problem/CodeForces-337D
*/
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int num=1e5+2;
struct node{
int v,next;
}e[num<<1];
int head[num],cnt,ans,dp[num][3],vs[num];
bool p[num];
int n,m,k;
void int_i(void)
{
memset(head,-1,sizeof(head));
cnt=ans=0;
memset(dp,-1,sizeof(dp));
}
inline void addedge(int u,int v)
{
e[++cnt].v=v;e[cnt].next=head[u];head[u]=cnt;
}
void dfs(int u,int f)
{
if(p[u]) dp[u][0]=dp[u][1]=0;
for(int i=head[u];i!=-1;i=e[i].next)
{
int v=e[i].v;
if(v==f) continue;
dfs(v,u);
if(dp[v][0]==-1) continue;//限制
if(dp[v][0]+1>=dp[u][0]) {dp[u][1]=dp[u][0];dp[u][0]=dp[v][0]+1;}
else if(dp[v][0]+1>dp[u][1]) dp[u][1]=dp[v][0]+1;
}return ;
}
void dfs2(int u,int f)
{
for(int i=head[u];i!=-1;i=e[i].next)
{
int v=e[i].v;
if(v==f) continue;
if(p[v]) dp[v][2]=0;
int t=max(dp[u][2],dp[u][0]==dp[v][0]+1?dp[u][1]:dp[u][0]);
if(t>=0) dp[v][2]=t+1;//限制
dfs2(v,u);
}
}
int main()
{
int_i();
int u,v;
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=m;i++) {scanf("%d",&u);p[u]=1;}
for(int i=1;i< n;i++) {scanf("%d%d",&u,&v);addedge(u,v);addedge(v,u);}
dfs(1,-1);
dfs2(1,-1);
for(int i=1;i<=n;i++) ans+=(dp[i][0]<=k&&dp[i][2]<=k);
// for(int i=1;i<=n;i++) printf("(%d,%d)n",dp[i][0],dp[i][2]);
// printf("n");
printf("%d",ans);
return 0;
}