树形dp

树形dp

1

n n 个点的树,边有边权,把其中k 个点涂成黑色,其余的点涂成白色,最大化:每两个黑点间距离和+每两个白点间的距离和。

直接设 f[i][j] f [ i ] [ j ] 表示 i i 子树里选 j 个黑点的最大值。然后发现不能转移…

考虑到每条边是独立的,因此这样设状态:令 f[i][j] f [ i ] [ j ] 表示 i i 的子树里选 j 个黑点最多对答案有多少贡献。

一条边对答案的贡献就是 权值 × × (左边黑点数 × × 右边黑点数 + + 左边白点数 × 右边白点数)。

void dfs(int u,int fa)
{
    size[u]=1;
    memset(f[u],-1,sizeof(f[u]));
    f[u][0]=f[u][1]=0;
    for(int i=head[u];i;i=ed[i].next)
    {
        int v=ed[i].to;
        if(v==fa) continue;
        dfs(v,u);
        size[u]+=size[v];
    }
    for(int i=head[u];i;i=ed[i].next)
    {
        int v=ed[i].to;
        if(v==fa) continue;
        for(int j=min(size[u],k);j>=0;j--)
        {
            for(int l=0,lim=min(size[v],j);l<=lim;l++)
            {
                if(~f[u][j-l])
                {
                    ll val=1ll*ed[i].w*(1ll*l*(k-l)+1ll*(size[v]-l)*(n-size[v]-k+l));
                    f[u][j]=max(f[u][j],f[u][j-l]+f[v][l]+val);
                }
            }
        }
    }
}

2

【SDOI 2008】山贼集团
题意: n n 个点的树,根为 1 ,设置 p p 个黑点(有编号) (p<=12) ,每个黑点可以管理它到根的路径上的每一个点,某些黑点同时管理同一个点会获得一定价值/损失,在不同的点设置不同的黑点要花费不同的代价,要求最大化收益。
p<=12 p <= 12 状压。 f[i][j] f [ i ] [ j ] 表示 i i 为根的子树内选择 j 的集合的最大收益。转移的时候先不考虑 i i ,令 g[j] 表示 i i 的子树中除去 i 的点选择 j j 的集合的最大价值(类似一个背包)。然后考虑 i 这个点放那些黑点,转移一下。

void dfs(int u,int fa)
{
    f[u][0]=0;
    for(int i=head[u];i;i=ed[i].next)
    {
        int v=ed[i].to;
        if(v==fa) continue;
        dfs(v,u);
        for(int j=0;j<=fff;j++) tmp[j]=-INF;
        tmp[0]=0;
        for(int j=0;j<=fff;j++)
            for(int k=j;k<=fff;k=(k+1)|j) tmp[k]=max(tmp[k],f[u][k^j]+f[v][j]);
        for(int j=0;j<=fff;j++) f[u][j]=tmp[j];
    }
    for(int j=0;j<=fff;j++) tmp[j]=f[u][j];
    memset(cost,0,sizeof(cost));
    for(int j=0;j<=fff;j++)
        for(int i=1;i<=p;i++)
            if(j&(1<<i-1)) cost[j]+=a[u][i];
    for(int i=0;i<=fff;i++) f[u][i]=-INF;
    for(int j=0;j<=fff;j++)
    {
        for(int k=j;k;k=(k-1)&j) f[u][j]=max(f[u][j],tmp[k^j]+w[j]-cost[k]);
        f[u][j]=max(f[u][j],tmp[j]+w[j]);
    }
}
int main()
{
    scanf("%d%d",&n,&p);
    fff=(1<<p)-1;
    for(int i=1;i<n;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        add(u,v),add(v,u);
    }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=p;j++) scanf("%d",&a[i][j]);
    scanf("%d",&T);
    for(int i=1;i<=T;i++)
    {
        int v,c,sta=0;
        scanf("%d%d",&v,&c);
        int ss=0;
        if(c==0) while(1);
        for(int j=1;j<=c;j++)
        {
            int x;
            scanf("%d",&x);
            sta|=(1<<x-1);
        }
        val[sta]+=v;
    }
    for(int j=0;j<=fff;++j)
        for(int k=j;k;k=j&(k-1)) w[j]+=val[k];
    for(int i=1;i<=n;i++)
        for(int j=0;j<=fff;j++) f[i][j]=-INF;
    dfs(1,1);
    cout<<f[1][fff];
    return 0;
}

3

【JSOI 2018】潜入行动
题意: n n 个点的树,设置 k 个监察点,每个监察点可以监视与它相邻的点,但不能监视自己。要监视所有点,求方案数。

f[i][j][1/0][1/0] f [ i ] [ j ] [ 1 / 0 ] [ 1 / 0 ] 表示 i i 子树里设置 k 个监察点,根有/没有设置监察点,有/没有被监视。然后分情况转移。

void dfs(int u,int fa)
{
    size[u]=1,f[u][0][0][0]=1,f[u][1][1][0]=1;
    for(int tt=head[u];tt;tt=ed[tt].next)
    {
        int v=ed[tt].to;
        if(v==fa) continue;
        dfs(v,u);
        int lim1=min(k,size[u]);
        for(int j=0;j<=lim1;j++)
        {
            g[j][0][0]=f[u][j][0][0],f[u][j][0][0]=0;
            g[j][0][1]=f[u][j][0][1],f[u][j][0][1]=0;
            g[j][1][0]=f[u][j][1][0],f[u][j][1][0]=0;
            g[j][1][1]=f[u][j][1][1],f[u][j][1][1]=0;
        }
        for(int i=0;i<=lim1;i++)
        {
            int lim2=min(size[v],k);
            for(int j=0;j<=lim2&&i+j<=k;j++)
            {
                f[u][i+j][0][0]+=(ll)1ll*g[i][0][0]*f[v][j][0][1]%mod;
                f[u][i+j][0][0]%=mod;
                f[u][i+j][0][1]+=(ll)1ll*g[i][0][1]*(f[v][j][0][1]+f[v][j][1][1])%mod+1ll*g[i][0][0]*f[v][j][1][1]%mod;
                f[u][i+j][0][1]%=mod;
                f[u][i+j][1][0]+=(ll)1ll*g[i][1][0]*(f[v][j][0][0]+f[v][j][0][1])%mod;
                f[u][i+j][1][0]%=mod;
                f[u][i+j][1][1]+=(ll)1ll*g[i][1][1]*(f[v][j][0][0]+f[v][j][1][0]+f[v][j][0][1]+f[v][j][1][1])%mod+1ll*g[i][1][0]*(f[v][j][1][0]+f[v][j][1][1])%mod;
                f[u][i+j][1][1]%=mod;
            }
        }
        size[u]+=size[v];
    }
}
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<n;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        add(u,v),add(v,u);
    }
    dfs(1,0);
    cout<<(f[1][k][1][1]+f[1][k][0][1])%mod;
    return 0;
}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
树形动态规划(Tree DP)是一种常用的动态规划算法,用于解决树结构相关的问题。在Python中,可以使用递归或者迭代的方式实现树形DP树形DP的基本思想是,从树的叶子节开始,逐层向上计算每个的状态,并利用已经计算过的节状态来更新当前节的状态。这样可以通过自底向上的方式,逐步计算出整个树的最优解。 下面是一个简单的示例,演示如何使用树形DP解决一个二叉树中节权值之和的最大值问题: ```python class TreeNode: def __init__(self, val=0, left=None, right=None): self.val = val self.left = left self.right = right def max_sum(root): if root is None: return 0 # 递归计算左右子树的最大权值和 left_sum = max_sum(root.left) right_sum = max_sum(root.right) # 当前节的最大权值和为当前节值加上左右子树中较大的权值和 return root.val + max(left_sum, right_sum) # 构建一个二叉树 root = TreeNode(1) root.left = TreeNode(2) root.right = TreeNode(3) root.left.left = TreeNode(4) root.left.right = TreeNode(5) # 计算二叉树中节权值之和的最大值 result = max_sum(root) print(result) ``` 这段代码中,我们定义了一个`TreeNode`类来表示二叉树的节,其中`val`表示节的权值,`left`和`right`分别表示左子节和右子节。`max_sum`函数使用递归的方式计算二叉树中节权值之和的最大值,通过比较左右子树的最大权值和来确定当前节的最大权值和。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值