DP-树形DP-luogu1352-上司的舞会

题目大意

求一棵树的最大独立集(1 <= n <= 6000)

6000也太水了,一般都是1e6的规模

问题分析

  1. 问题抽象来说是最大独立集,具体在树中的体现题中已经明示
  2. 图G(V, E)的最大独立集S(V’, E’)具有如下性质:E’ = {},即最大独立集中的点两两之间没有边连接
  3. dp[i][?]为节点i在?情况下的最大独立集总权重,? = 0 和 ? = 1分别表示不取/选取这一点,则
    d p f , 0 = ∑ m a x { d p s o n , 0 , d p s o n , 1 } d p f , 1 = ∑ d p s o n , 0   +   r f \begin{aligned} dp_{f,0} &= \sum max\{dp_{son,0}, dp_{son, 1}\} \\ dp_{f,1} &= \sum dp_{son,0} \ + \ r_f \end{aligned} dpf,0dpf,1=max{dpson,0,dpson,1}=dpson,0 + rf
  4. 显然,父节点的计算依赖于子节点的计算,所以必须有顺序地计算dp,有一下几种手段:
    • 从root节点dfs,回溯时计算(一条链时可能爆栈
    • 拓扑排序,依次计算(十分科学
    • 从root节点bfs层序遍历,然后在queue中逆序计算(利用树的性质,Nice!
    • 标记每个节点子节点计算ok的数量,如果ok[i]==false && ok_son[i] = son[i].size,则计算这一点(基本保证每个点只算一次,但是无用的空循环可能不少,故不推荐
  5. 类似地树形DP利用的都是父子节点间的转移关系
  6. 下面的代码就是采用拙劣的标记法

AC代码

#include <cstring>
#include <cstdio>
#include <algorithm>
#include <vector>

using namespace std;
const int maxn = 6e3 + 10;
const bool debug = false;

int n;
int r[maxn];
vector<int> son[maxn];
int father[maxn];
int ok_son[maxn];
bool ok[maxn];

int dp[maxn][2];

void solve()
{
    int root, cnt = n;
    while(cnt>0){
        if(debug)printf("Round #%d:\n", cnt-1);
        for(int i=1;i<=n;i++){
            if(!ok[i] && ok_son[i] == (int)son[i].size()){
                root = i;
                ok[i] = true, cnt--;
                if(debug)printf("%d ",i);
                if(son[i].size() == 0){
                    dp[i][0] = 0;
                    dp[i][1] = r[i];
                    ok_son[father[i]]++;
                }else{
                    ok_son[father[i]]++;
                    dp[i][1] += r[i];
                    for(int j:son[i]){
                        dp[i][0] += max(dp[j][0], dp[j][1]);
                        dp[i][1] += dp[j][0];
                    }
                }
            }
        }
        if(debug)printf("\n");
    }
    if(debug)printf("root = %d\n", root);

    printf("%d\n", max(dp[root][0], dp[root][1]));

}

int main()
{
    scanf("%d", &n);
    for(int i=1;i<=n;i++)
        scanf("%d", &r[i]);
    for(int i=0;i<n-1;i++)
    {
        int l,k;
        scanf("%d%d", &l, &k);
        father[l] = k;
        son[k].push_back(l);
    }
    int t1, t2;
    scanf("%d%d", &t1, &t2);
    solve();
    if(debug){
        printf("  i  0  1\n");
        printf("=========\n");
        for(int i=1;i<=n;i++)
            printf("%3d%3d%3d\n", i, dp[i][0], dp[i][1]);

    }
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值