cpp怎么给int dp[]设置初始值_20200224_树形dp

<!-- 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. 具体代码

<!-- /TOC --> 20200224树形dp

1. 一,回顾:

之前关于树形dp的文章:

  1. 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. 题目解析
  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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值