树型DP求树的最小支配集

树型DP求树的最小支配集

定义:

支配集:形式上,支配集可描述如下:给定无向图G =〈V , E〉,其中V 是大小为n 的点集,E 是边集,那么V 的一个子集S称为支配集当且仅当对于V - S 中任何一个点v ,都有S 中的某个定点u , 使得( u , v) ∈E。

最小支配集:对于图G = (V, E) 来说,最小支配集指的是从 V 中取尽量少的点组成一个集合, 使得V 中剩余的点都与取出来的点有边相连.也就是说,设 V' 是图的一个支配集,则对于图中的任意一个顶点 u ,要么属于集合 V', 要么与V' 中的顶点相邻. 在 V' 中除去任何元素后 V' 不再是支配集, 则支配集 V' 是极小支配集.称G 的所有支配集中顶点个数最少的支配集为最小支配集,最小支配集中的顶点个数称为支配数.

贪心法(点这里)

树型DP:

思路:

首先我们观察,一个点被覆盖只有三种情况:
1.
被它自己覆盖
2.被它的儿子节点覆盖
3.被它的父亲覆盖

根据以上的分析,我们就可以令F[u][0],F[u][1],F[u][2]分别表示u在上述的三种情况能得到的最小值。

补充:官方解释:

1):F[i][0],表示点 i 属于支配集合,并且以点 i 为根的子树都被覆盖了的情况下支配集中所包含最少点的个数.

2):F[i][1],表示点 i 不属于支配集合,且以 i 为根的子树都被覆盖,且 i 被其中不少于一个子节点覆盖的情况下支配集所包含最少点的个数.

3):F[i][2],表示点 i 不属于支配集合,且以 i 为根的子树都被覆盖,且 i 没被子节点覆盖的情况下支配集中所包含最少点的个数.即 i 将被父节点覆盖.

 

推导动态规划方程:

来看简单点的两种情况F[u][0]和F[u][2](下面我们均用v来代表u的子节点)
1.

F[u][0]代表的是u点选择u点进行覆盖,那么经过推到我们可得

 

  F[u][0]=minΣ(F[v][0],f[v][1],f[v][2])

2.

而F[u][2]则代表的是u被其父节点覆盖,这同样也代表着u不是被选中的点,也就是说u的子节点v的F[v][2]不放入考虑范围(因为F[v][2]代表的是v被父节点u覆盖,但u又不选,所以加入会矛盾)

 

F[u][2]= Σmin(F[v][0], F[v][1])

3.

 

F[u][1]:它代表的是u节点被其子节点覆盖。

这里和上面不同,这里说的覆盖可以>=1个子节点。

那么对于F[u][1]来说,它可以从v的哪些状态得到呢?

经过简单的思考,我们得出F[u][1]可以从F[v][1]和F[v][0]得到(为什么不能是F[v][2]呢?前面已经讲过了,既然选择F[u][1]计算,也就意味着u点不被选择去覆盖别的点)。但动态规划方程

 

F[u][1]= Σmin(F[v][0], F[v][1])

 

这样不就和F[u][2]一样了吗?

但我们会发现一个问题,这样写出的方程在转移时可能出现问题,就是有一种特殊情况:有可能出现在求和时每一个v都取的是F[v][1],这也就意味着我们最后求出来的F[u][1]代表的解中u的子节点v都没有被选择去覆盖别的点!,而这与我们对F[u][1]的定义是矛盾的。

怎么办呢?我们要用一个bool类型的变量flag来记录下F[u][1]是否从F[v][0]这种状态转移过来过,如果没有,则要强制让一个从F[v][1]转移过来的变成从F[v][0]转移过来,并且还要保证尽量小。

最后的问题是,如何强制呢?我们令一个变量inc=min(F[v][0]-F[v][1]),那么如果最后检查flag标记时若发现F[u][1]没有从F[v][0]转移来的历史记录,我们就F[u][1]+=inc就可以啦(这就相当于强制让一个从F[v][1]转移过来的变成从F[v][0]转移过来,并保证了最小),所以最后F[u][1]的状态转移方程就是

  

F[u][1]=Σmin(F[v][0],F[v][1])果u子节点v有被选择的)

F[u][1]=Σmin(F[v][0],F[v][1])+inc果u子节点v没有被选择的)

inc=min(F[v][0]F[v][1])

最后的最后要注意的地方就是最后输出答案的时候是min(F[1][0],F[1][1]),而不是min(F[1][0],F[1][1],F[1][2])(1就是根节点,肯定不会被其“父节点”覆盖)

例题:(点这里)

代码模板:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<string>
#include<cstdlib>
#include<queue>
#include<vector>
#define INF 0x7fffffff
#define PI acos(-1.0)
#define N 10025
#define MOD 2520
#define E 1e-12
using namespace std;
#define N 100005
int f[N][3],n,vis[N];
vector<int> g[N];
int dp(int u)
{

    f[u][0]=1;
    f[u][1]=0;
    f[u][2]=0;
    vis[u]=1;
    bool flag=0;
    int inc=INF;

    for(int i=0;i<g[u].size();i++)
    {
        int v=g[u][i];

        if(vis[v]==0)
        {

            dp(v);
        f[u][0]+=min(f[v][0],min(f[v][1],f[v][2]));
        f[u][2]+=min(f[v][0],f[v][1]);

        if(f[v][0]<=f[v][1])
          {
            flag=1;
            f[u][1]+=f[v][0];
          }
          else
          {
              inc=min(inc,(f[v][0]-f[v][1]));
              f[u][1]+=f[v][1];
          }
        }
    }
    if(flag==0)//f[u][1]没有从f[v][0]推过来,强制转化为从f[v][0]推过来
        f[u][1]+=inc;
    return 0;
}
int main()
{
    while(scanf("%d",&n)!=EOF)
    {
        memset(f,0,sizeof(f));
            memset(vis,0,sizeof(vis));
      for(int i=1;i<n;i++)
      {
          int x,y;
          scanf("%d%d",&x,&y);
          g[x].push_back(y);
          g[y].push_back(x);
      }
      dp(1);
      printf("%d",min(f[1][0],f[1][1]));
    }
    return 0;
}

例题(点这里)

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值