树形dp摸瞎历程

树形\(dp\)摸瞎历程

前言:

  • 什么是树形\(dp\)?

简而言之,树形dp,就是在树形结构上的动态规划,由于树形结构具有一定的特点,可以描述比较复杂的关系,再加上树的递归定义,是一种非常合适动规的框架,属于动规中很特殊的一种类型。

  • 如何实现树形\(dp\)?

    树形dp的状态表示中,第一位通常是节点编号(代表以该节点为根的子树),大多数时候,我们采用递归的方式实现树形动态规划。对于每个节点x,我们先递归x的所有子节点,并在其子节点上dp,在回溯时,从子节点向节点x进行状态转移。

  • 树形\(dp\)的分类
    • 选择节点类:给定一棵树,每个节点都有一定的值,让你在树上选择任意数量的点,但要满足一定的父子关系(比如,\(fa\)和\(son\)要共存,或者不能共存),求最大或最小的贡献。

    常用dp套路:

    定义状态:f[i][0/1]表示在以i号节点为根的子树中,i号节点是(1)否(1)选择所能造成的最大或最小贡献.
    • 分组背包类:给定一棵树,节点或者树边有一定的值,让你在树上选择有限数量的点或边,求最大或最小贡献。
    定义状态:f[i][j]表示在以i号节点为根的子树中,选择j个节点或边所得的最大值或最小值
    • 以上两种是比较常见的,但树形dp远远不止上述两类,后文会提到一些其他类型的。
  • 递归实现树形dp的方法
    • 对于无根树,即题目强制要求无向边,可用如下模板递归:
    inline void dfs(int x,int fa)//fa是x的父亲节点
    {
      for(int i=head[x];i;i=Next[i])//邻接表常规便利
      {
          int y=ver[i];
          if(y==fa) continue;
          dfs(y,x);
          /*dp具体内容*/
      }
    }
  • 对于有根树,即从题面中我们很容易可以看出是一张有向图(如“没有上司的舞会”),对于这种情况我们可以加两次边,把它当做无根树用上述模板来处理,或者在原有向图中找出一个根,再以此递归。
inline void dfs(int x)
{
    for(int i=head[x];i;i=Next[i])
    {
        int y=ver[i];
        dfs(y);
        //dp
    }
}//找根可以用其入度为零这一特点来找,这里不再介绍。

例题选讲:

注:后文代码仅给出核心部分,涉及到的基础函数将在这里给出

邻接表加边函数:

inline void add(int x,int y,int z)
{
    ver[++tot]=y;
    Next[tot]=head[x];
    head[x]=tot;
    edge[tot]=z;
}

快读函数:

inline int read()
{
    int num=0,w=1;char ch=getchar();
    while(ch<'0' || ch>'9'){if(ch=='-') w=-1;ch=getchar();}
    while(ch<='9' && ch>='0')
        num=(num<<1)+(num<<3)+ch-'0',ch=getchar();
    return  num*w;
}

宏定义:

#define Mi return
#define manchi 0

后文除第一个例题外,都只叙述无向图的解法。


选择节点类:
没有上司的舞会

很容易看出这是个选择节点类的树形dp,因为题目对于选择职员的数量并没有限制。

那么我们依据套路定义dp状态:

定义状态:
f[i][1]表示在以i号节点为根的子树中,i号职员来参加舞会所得的最大快乐指数
f[i][0]则表示i号职员不来参加所得的最大快乐指数

接下来我们明确父子关系:如果某个职员的上司来参加舞会了,那么这个职员就无论如何也不肯来参加舞会了,即父亲与儿子不能共存。

然后以此进行状态转移:

状态转移:
f[i][1]+=max(f[son][0])//由于i去了,所以i的儿子就不能去了,即f[son][0]
f[i][0]+=max(f[son][1],f[son][0])//i没有去,那么i的儿子可去可不去

考虑边界:

f[i][1]=happy[i]//初始状态去参加舞会的快乐指数肯定等于自身的快乐指数

然后套上dfs就能A掉了,\(QwQ\).

Code(有向图):

const int N = 6000+5;

int n,happy[N],root;
int head[N],ver[N<<1],tot,Next[N<<1];
int f[N][3];bool vis[N];//vis数组用于找根
//f[i][0]以i为根,i不去;f[i][1]以i为根,i去
//f[i][0]+=max(f[j][0],f[j][1]),j是i的儿子
//f[i][1]+=max(f[j][0]) 

inline void slove(int x)
{
    for(int i=head[x];i;i=Next[i])
    {
        int y=ver[i];
        slove(y);
        //dp
        f[x][0]+=max(f[y][0],f[y][1]);
        f[x][1]+=f[y][0];
    }
}

int main()
{
    n=read();
    for(int i=1;i<=n;i++)
        f[i][1]=read();//直接读入的时候初始化
    for(int i=1;i<=n-1;i++)
    {
        int x=read(),y=read();
        vis[x]=1;add(y,x);
    }int a=read(),b=read();

    //找根
    for(int i=1;i<=n;i++)
        if(!vis[i]){root=i;break;}

    slove(root);
    cout<<max(f[root][1],f[root][0]);//输出答案
    return 0;
}

Code(无向图):

inline void dfs(int x,int fa)
{
    for(int i=head[x];i;i=Next[i])
    {
        int y=ver[i];
        if(y==fa) continue;
        dfs(y,x);
        f[x][0]+=max(f[y][0],f[y][1]);
        f[x][1]+=f[y][0];
    }
}

int main()
{
    n=read();
    for(int i=1;i<=n;i++)
        f[i][1]=read();
    for(int i=1;i<=n-1;i++)
    {
        int x=read(),y=read();
        add(y,x);add(x,y);//注意加两次边,转为无向图
    }int a=read(),b=read();
    dfs(1,0);//任选一个点作为根进行dp,这里选择1作为根.
    cout<<max(f[1][1],f[1][0]);
    return 0;
}

最大子树和

由于对于选择的花数量没有限制,这显然是一道节点选择类的树形dp,再确定父子关系:父亲不选择,儿子就无法选择(这点显然),那就直接走套路就行了

const int N = 16000+5;

/*
定义状态:
f[i][1]表示在以i号节点为根的子树中,选择第i号花所得最大美丽指数
f[i][0]则表示不选择i号花的最大美丽指数

状态转移:
f[i][1]+=max(f[son][0],f[son][1])
f[i][0]=0

边界:
f[i][1]=a[i]
*/ 

int n,f[N][2],root,a[N],ans=-0x7fffffff;
int ver[N<<1],head[N<<1],Next[N<<1],tot;

inline void dfs(int x,int fa)
{
    for(int i=head[x];i;i=Next[i])
    {
        int y=ver[i];
        if(y==fa) continue;
        dfs(y,x);
        f[x][0]=0;
        f[x][1]+=max(f[y][1],f[y][0]);
    }
}

int main()
{
    n=read();
    for(int i=1;i<=n;i++) f[i][1]=read();
    for(int i=1;i<=n-1;i++)
    {
        int x=read(),y=read();
        add(y,x),add(x,y);
    }
    dfs(1,0);
    for(int i=1;i<=n;i++)
        ans=max(ans,f[i][1]);
    printf("%d",ans);
    Mi manchi;
}

战略游戏

显然是一道节点选择类的树形dp,确定父子关系:父亲和儿子中有一个存在即可(只要存在一个,那么父亲与儿子之间的这条边就可以被覆盖)。

都是套路\(QwQ\)

const int N = 1500+5;
/*
定义状态:
f[i][0]表示以i为根的子树,i结点不放士兵的最小值
f[i][1] 放士兵的最小值

状态转移:
f[i][0]+=f[son][1]//父亲不选择的话,儿子就一定要选择才符合题意
f[i][1]+=min(f[son][1],f[son][0]);//父亲选择了的话,那么儿子选不选都可以. 

边界:
f[i][1]=1
ans=min(f[i][1],f[i][0])
*/
int n,f[N][2];
int tot,head[N],ver[N],Next[N];

inline void dfs(int x,int fa)
{
    f[x][1]=1;
    for(int i=head[x];i;i=Next[i])
    {
        int y=ver[i];
        if(y==fa) continue;
        dfs(y,x);
        f[x][1]+=min(f[y][1],f[y][0]);
        f[x][0]+=f[y][1];
    }
}

int main()
{
    n=read();
    for(int i=1;i<=n;i++)
    {
        int x=read(),k=read();
        for(int j=1;j<=k;j++)
        {
            int y=read();
            add(x,y);add(y,x);
        }
    }
    dfs(1,-1);//注意是从节点0开始编号的,所以我们选的root的fa不能为0
    printf("%d",min(f[1][0],f[1][1]));
    Mi manchi;
}

上面都是挺水的题目,接下来看一道有点思维难度的题目。


保安站岗

我们发现这题和战略游戏那题很像,但是战略游戏关注的是边,而这题关注的是点有没有被覆盖,所以我们就要重新确定父子关系,并重新定义状态。

父子关系:
自己(i),父亲(fa),儿子(son)中必须要存在一个
定义状态:
f[i][0]表示i被自己覆盖 的最小花费
f[i][1]表示i被儿子覆盖 的最小花费
f[i][2]表示i被父亲覆盖 的最小花费
状态转移:
1.f[i][0]+=min(f[son][1],f[son][2],f[son][0])
2.f[i][1]=f[x][0]+sigma(min (f[son][0],f[son][1]) )
3.f[i][2]+=min(f[son][0],f[son][1])

我们分析一下上面状态转移的过程:
1.此时情况是节点i处放置警察,那么它的儿子可以选择被自己看守(f[son][0]),也可以选择被儿子看守(f[son][1]),当然也可以选择被父亲看守(f[son][2])
转移2有点小难度,我们先分析转移3
3.此时的情况是节点i被父亲覆盖(即i节点没有放置警察),那i的儿子(son)就只能选择被自己看守(f[son][0]),或者被它的儿子的看守(f[son][1])

现在讨论转移2:

此时的情况是节点\(i\)处不放置警察,节点\(i\)由它的儿子看守,那么我们显然要在\(i\)的众多儿子中,选择一个儿子(对应上面转移方程中的\(x\))放置警察,这样当前节点\(i\)才会被看守到,然后对于剩余的儿子,我们进行和转移方程3一样的操作就可以了,因为此时节点\(i\)没有放置警察,那\(i\)的儿子就只能选择被自己看守,或者被它的儿子的看守。

那么我们只需要找到对于节点\(i\)来说,一个最优的儿子\(x\)就行了,可以考虑枚举所有儿子,但是也有数学方法来优化,以下内容参考

题解 P2458保安站岗

对于x来说,有\(f[i][1]=f[x][0]+\Sigma_{j\subset son(i),j!=x}{min(f[j][0],f[j][1])}\)

若x不是最优的,则存在y满足\(f[x][0]+\Sigma_{j\subset son(i),j!=x}{min(f[j][0],f[j][1])}<f[y][0]+\Sigma_{j\subset son(i),j!=y}{min(f[j][0],f[j][1])}\)

合并同类项,整理得\(f[x][0]-min(f[x][0],f[x][1])>f[y][0]-min(f[y][0],f[y][1])\)

所以对于最优的x,只需要满足\(f[x][0]-min(f[x][0],f[x][1])\)是所有儿子中最小的就可以了。

我们不妨设最优的\(x\)一开始为0,然后对\(f[0][0]\)赋一个极大值来转移

const int N = 1500+5;
int n,f[N<<1][3];
int tot,head[N],ver[N<<1],Next[N<<1];

inline void dfs(int x,int fa)
{
    int special_son=0;
    for(int i=head[x];i;i=Next[i])
    {
        int y=ver[i];
        if(y==fa) continue;
        dfs(y,x);
        f[x][0]+=min(f[y][0],min(f[y][1],f[y][2]));
        f[x][2]+=min(f[y][0],f[y][1]);
        //找最优的x
        if((f[special_son][0]-min(f[special_son][0],f[special_son][1])) > (f[y][0]-min(f[y][0],f[y][1])))
            special_son=y;
    }
    //找到x之后,我们还需要求出simga(min (f[j][0],f[j][1]) )
    f[x][1]=f[special_son][0];
    for(int i=head[x];i;i=Next[i])
    {
        int y=ver[i];
        if(y==fa || y==special_son) continue;
        f[x][1]+=min(f[y][0],f[y][1]);
    }
}

int main()
{
    n=read();
    for(int i=1;i<=n;i++)
    {
        int x=read(),val=read(),k=read();
        f[x][0]=val;//初始化
        for(int j=1;j<=k;j++)
        {
            int y=read();
            add(x,y);add(y,x);
        }
    }
    f[0][0]=0x3f3f3f3f;dfs(1,-1);//赋初值
    printf("%d",min(f[1][0],f[1][1]));
    Mi manchi;
}


通过上一道例题我们可以发现,节点选择类的树形dp,其状态的第二维,就不只局限于是否选择当前这个节点,而是针对题意拓展为当前这个点的状态是什么,就如上一道例题,状态的第二维表示的是节点\(i\)的具体被看守情况。我们再来看一道例题。

三色二叉树

这道题目相对简单,可以说是节点选择类dp的进阶练手题,此时我们如果还用\(f[i][0/1]\)来表示i节点是否染成绿色的话,显然是无法解决问题的,所以我们要考虑拓展第二维,这拓展是很好想的。由于最大值和最小值得求法本质是类似的,故下面只叙述最大值的做法

定义状态:
f[i][0/1/2]表示在以i为根的子树中,染色绿色的最多个数
且i的颜色为 0/1/2 (绿/红/蓝)

接下来我们确定父子关系,然后依据具体情况转移就行了。

明确父子关系:父亲和儿子状态不能相同,儿子和儿子之间状态也不能相同

状态转移:
1.i为绿色,那么左右儿子只能为红或蓝
f[i][0]=max(f[lson][1]+f[rson][2],f[lson][2]+f[ron][1])+1
2.i为红/蓝
f[i][1]=max(f[lson][0]+f[rson][2],f[lson][2]+f[rson][0]);
f[i][2]=max(f[lson][1]+f[rson][0],f[lson][0]+f[rson][1]);
初始f[i][0]=1,f[i][1/2]=0;
ans=max(f[1][0/1/2]) 

本题对于树的结构是有严格限制的,即只能有一个儿子或者两个儿子,那么我们就可以用数组来模拟这棵树,而非用领接表加边建树。

using namespace std;
const int N = 500000+5;

int f[N][3],g[N][3];
int tot,tree[N][2];
//数组模拟这棵树,tree[i][0/1]表示以i为根的树中,左/右儿子的编号
char TREE[N];

inline void build(int root)//递归建树
{
    tot++;
    if(TREE[root]=='0')  return;
    else if(TREE[root]=='1') {tree[root][0]=tot+1;build(tot+1);return;}
    else if(TREE[root]=='2') {tree[root][0]=tot+1;build(tot+1);tree[root][1]=tot+1;build(tot+1);return;}
}

inline void dfs(int fa)
{
    f[fa][0]=g[fa][0]=1;
    if(tree[fa][0] && tree[fa][1])//左右儿子都有
    {
        int l=tree[fa][0],r=tree[fa][1];
        dfs(l);dfs(r);
        f[fa][0]=max(f[l][1]+f[r][2],f[l][2]+f[r][1])+1;
        f[fa][1]=max(f[l][0]+f[r][2],f[l][2]+f[r][0]);
        f[fa][2]=max(f[l][1]+f[r][0],f[l][0]+f[r][1]);
        g[fa][0]=min(g[l][1]+g[r][2],g[l][2]+g[r][1])+1;
        g[fa][1]=min(g[l][0]+g[r][2],g[l][2]+g[r][0]);
        g[fa][2]=min(g[l][1]+g[r][0],g[l][0]+g[r][1]);
    }
    else if(tree[fa][0] && !tree[fa][1])
    {
        int son=tree[fa][0];
        dfs(son);
        f[fa][0]=max(f[son][1],f[son][2])+1;
        f[fa][1]=max(f[son][0],f[son][2]);
        f[fa][2]=max(f[son][0],f[son][1]);
        g[fa][0]=min(g[son][1],g[son][2])+1;
        g[fa][1]=min(g[son][0],g[son][2]);
        g[fa][2]=min(g[son][0],g[son][1]);
    }
}

int main()
{
    scanf("%s",TREE+1);build(1);
    dfs(1);
    printf("%d ",max(f[1][0],max(f[1][1],f[1][2])));
    printf("%d",min(g[1][0],min(g[1][1],g[1][2])));
    Mi manchi;
}

叶子的染色

不是很人性的题意翻译:给定一棵树,要求在树中选择节点染成黑色或白色,使得从根节点到叶子结点的简单路径上至少包含一个有色节点,同时给出限制\(c[i]\),要求在从根节点到\(i\)号叶子节点的简单路径中,最后一个有色节点的颜色为\(c[i]\),求需要选择着色的最少节点数。

本质上仍然是节点选择类的树形dp,其状态与上一道例题很相似,这里先给出整体思路。

/*
定义状态:
f[i][0/1/2]表示在以i为根的子树中,i的颜色为(黑色/白色/不染色)所需染色的最小值
确定父子关系:
保证根结点到每个叶子的简单路径上都至少包含一个有色结点
状态转移:
贪心地去想,保证简单路径上都包含一个有色节点,显然是将这个有色节点放得越高越好,
即让经过这个有色节点的简单路径数增多.
考虑c[i]这个限制条件,我们只要固定第i个节点的颜色就可以了
1.如果i节点染成黑色,那我们的子节点就不用染成黑色。
由于是最少的着色节点,所以我们父亲的值显然为sigma(son),所以用f[i][]+=
f[i][0]+=min(f[son][0]-1,f[son][1],f[son][2])
2.如果i节点染成白色
f[i][1]+=min(f[son][0],f[son][1]-1,f[son][2])
3.如果当前节点不染色
不染色的实质是因为无法确定当前节点要染黑色还是白色,还要看fa的情况
f[i][2]+=min(f[son][0/1/2])
边界:f[i][0]=f[i][1]=f[i][2]=1;   f[i][2]的实质是染了一个不确定的颜色,所以其初始值也为1
f[i][!(c[i])]=INF;即i号节点绝对不能染成与c[i]相反的颜色,这点很显然
答案:ans=min(f[i][0/1/2])
*/

贪心策略:

若某结点的儿子结点中需要染黑色的比白色多,就将此节点标记为要染黑色,把要染白色的儿子染成白色;

例如节点\(i\)有三个儿子,其中两个儿子需要染成黑色才符合条件,另一个则需要染成白色,那么我们显然应该把节点\(i\)染成黑色,这样它的两个儿子就不需要染色了,只需要将另一个儿子染成白色即可。

儿子节点中需要染成白色的比黑色的多,同理。

那还有一种情况就是要染成黑色和白色的个数一样多,那么我们这时就考虑先不染色,看看\(i\)的兄弟的情况再具体染色,例如节点\(i,j\)是兄弟,其父亲为\(fa\),\(i\)节点暂时没有染色(可以染成任意颜色),\(j\)节点需要染成黑色,那么我们显然应该把\(i\)节点看成黑色,此时\(fa\)就需要染成黑色,\(i,j\)就不用染色,这样显然是最优的。

以上策略参考(建议阅读):

叶子的染色贪心做法

const int N = 100500;
const int INF = 0x3f3f3f3f;

int n,m,tot,c[N],f[N][3];
int ver[N<<1],head[N],Next[N<<1];

inline void dfs(int x,int fa)
{
    if(x<=m) return ;
    for(int i=head[x];i;i=Next[i])
    {
        int y=ver[i];
        if(y==fa) continue;
        dfs(y,x);
        f[x][0]+=min(f[y][0]-1,min(f[y][2],f[y][1]));
        f[x][1]+=min(f[y][0],min(f[y][2],f[y][1]-1));
        f[x][2]+=min(f[y][0],min(f[y][1],f[y][2]));
    }
}

int main()
{
    n=read();m=read();
    for(int i=1;i<=m;i++) c[i]=read();
    for(int x,y,i=1;i<n;i++)
    {
        x=read(),y=read();
        add(x,y);add(y,x);
    }
    for(int i=1;i<=n;i++)
    {
        f[i][0]=f[i][1]=f[i][2]=1;
        if(i<=m) f[i][!(c[i])]=INF;
    }
    dfs(m+1,-1);//以一个不是叶子节点的根 dfs
    printf("%d",min(f[m+1][1],min(f[m+1][0],f[m+1][2])));
    Mi manchi;
}


树形dp摸瞎历程

原文地址:https://www.cnblogs.com/fengzi8615/p/11768259.html

时间: 2024-07-30 07:37:46

树形dp摸瞎历程的相关文章

[poj2342]Anniversary party_树形dp

Anniversary party poj-2342 题目大意:没有上司的舞会原题. 注释:n<=6000,-127<=val<=128. 想法:其实就是最大点独立集.我们介绍树形dp 树形dp就是以节点或者及其子树为信息,进行动态规划.用dfs的原理,遍历,在回溯是更新父亲节点. 然后,关于这道题,我们就可以对于每一个节点进行标记,然后对于满足条件的节点进行遍历.设状态就是两个dp,分别表示选当前根节点和不选当前根节点.更新是瞎jb更新即可... .... 最后,附上丑陋的代码...

HDU-2196 Computer (树形DP)

最近在看树形DP,这题应该是树形DP的经典题了,写完以后还是有点感觉的.之后看了discuss可以用树分治来做,以后再试一试. 题目大意 找到带权树上离每个点的最远点.︿( ̄︶ ̄)︿ 题解: 对于每一个点的最远点,就是以这个点为根到所有叶子节点的最长距离.但是如果确定根的话,除了根节点外,只能找到每个节点(度数-1)个子树的最大值,剩下一个子树是该节点当前的父亲节点. 所以当前节点的最远点在当前节点子树的所有叶子节点以及父亲节点的最远点上(当父亲节点的最远点不在当前节点的子树上时), 如果父亲节

UVA-01220 Party at Hali-Bula (树形DP+map)

题目链接:https://vjudge.net/problem/UVA-1220 思路: 树形DP模板题,求最大人数很简单,难点在于如何判断最大人数的名单是否有不同的情况: 解决方法是用一个数组f[manx][2]记录该节点是否出场的情况,为真时代表有多种情况; 具体讨论: 当父节点的值加上某个子节点的值时,他的f的情况也和该子节点一样: 当某个节点dp(i, 0) == dp(i, 1), 则该节点以及它的父节点也一定有多种情况(父节点必定取其中之一). Code: 1 #include<bi

HDU 1520 树形dp裸题

1.HDU 1520  Anniversary party 2.总结:第一道树形dp,有点纠结 题意:公司聚会,员工与直接上司不能同时来,求最大权值和 #include<iostream> #include<cstring> #include<cmath> #include<queue> #include<algorithm> #include<cstdio> #define max(a,b) a>b?a:b using nam

HDU2196 Computer(树形DP)

和LightOJ1257一样,之前我用了树分治写了.其实原来这题是道经典的树形DP,感觉这个DP不简单.. dp[0][u]表示以u为根的子树中的结点与u的最远距离 dp[1][u]表示以u为根的子树中的结点与u的次远距离 这两个可以一遍dfs通过儿子结点转移得到.显然dp[0][u]就是u的一个可能的答案,即u往下走的最远距离,还缺一部分就是u往上走的最远距离: dp[2][u]表示u往上走的最远距离 对于这个的转移,分两种情况,是这样的: dp[2][v] = max( dp[0][u]+w

hdu5593--ZYB&#39;s Tree(树形dp)

问题描述 ZYB有一颗N个节点的树,现在他希望你对于每一个点,求出离每个点距离不超过KK的点的个数. 两个点(x,y)在树上的距离定义为两个点树上最短路径经过的边数, 为了节约读入和输出的时间,我们采用如下方式进行读入输出: 读入:读入两个数A,B,令fai??为节点i的父亲,fa?1??=0;fa?i??=(A∗i+B)%(i−1)+1,i∈[2,N] . 输出:输出时只需输出N个点的答案的xor和即可. 输入描述 第一行一个整数TT表示数据组数. 接下来每组数据: 一行四个正整数N,K,A,

CF 219D Choosing Capital for Treeland 树形DP 好题

一个国家,有n座城市,编号为1~n,有n-1条有向边 如果不考虑边的有向性,这n个城市刚好构成一棵树 现在国王要在这n个城市中选择一个作为首都 要求:从首都可以到达这个国家的任何一个城市(边是有向的) 所以一个城市作为首都,可能会有若干边需要改变方向 现在问,选择哪些城市作为首都,需要改变方向的边最少. 输出最少需要改变方向的边数 输出可以作为首都的编号 树形DP 先假定城市1作为首都 令tree(i)表示以i为根的子树 dp[i]表示在tree(i)中,若以i为首都的话,需要改变的边数 第一次

HDU 1011 Starship Troopers(树形dp+背包)

Starship Troopers Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 13109    Accepted Submission(s): 3562 Problem Description You, the leader of Starship Troopers, are sent to destroy a base of

Codeforces 462D Appleman and Tree 树形dp

题目链接:点击打开链接 题意: 给定n个点的树, 0为根,下面n-1行表示每个点的父节点 最后一行n个数 表示每个点的颜色,0为白色,1为黑色. 把树分成若干个联通块使得每个联通块有且仅有一个黑点,问有多少种分法(结果mod1e9+7) 思路: 树形dp,每个点有2个状态,已经归属于某个黑点和未归属于某个黑点. #include <cstdio> #include <vector> #include <iostream> using namespace std; #de