zoukankan      html  css  js  c++  java
  • bzoj 1495

    这是一道...卡了我一个月的树形dp...

    我真是太弱了...

    其实仔细想想,这题的核心思路并不是特别复杂,但是的确存在不小的难度

    作为一个看过全网基本所有题解+标程才弄明白这题到底怎么回事的蒟蒻,我努力把所有东西揉到一起让各位看官一眼看懂...

    首先我们简化一下题意:给定一棵满二叉树,每个叶节点有一个状态(0,1),任选两个叶节点,如果这两个叶节点状态相同但他们的LCA所管辖的子树中的与他们状态相同的叶节点个数较少(少于1/2),则会产生2f的代价,如果状态不同,则产生f的代价,如果状态相同且LCA管辖子树中与他们状态相同叶节点个数较多,则不产生代价,现在每个节点可以变更状态,但变更状态也有自己的代价,求最小总代价

    那么..怎么搞?

    首先,有一个很重要的思想:点对之间的问题是难以快速解决的!

    很简单,因为如果我们按点对统计贡献,那么在枚举到每个叶节点都需要考虑其他所有叶节点,这样是不利于我们处理问题的

    所以我们第一步要考虑的是把点对的贡献压到一个点上

    很幸运,题目给出了这样的方式:

    注意题目中“产生2f的代价”“产生f的代价”这两句话!

    那么如果我们同样给所有非叶节点一个状态(0,1),代表这个节点管辖的子树中叶节点状态为0的多还是状态为1的多,那么我们显然可以看到:如果一个叶节点和这个根节点的状态相同,那么这个叶节点不会产生贡献,反之会产生一个f的贡献!

    证明:分类讨论:

    ①:假设两个节点与根节点状态都相同,那么这两个节点都不产生贡献,满足题意

    ②:假设两个节点与根节点状态都不同,那么这两个节点都产生一个f的贡献,满足题意

    ③:如果有一个点与根节点状态相同,那么这两个节点只产生一个f的贡献,同样满足题意

    因此我们这样处理点对是正确的

    总结:在处理点对时,我们要把一个点对的贡献压到一个点上,同时给所有非叶节点一个状态,这样就能满足题意了

    接下来就可以进行树形dp了

    记状态dp[i][j]表示当前位于树上的第i个节点,其子树的叶节点中状态为0的点的个数为j时所需的最小代价

    (这里对题意有一个小提示:当两种状态的叶节点数量相等时,认为状态为0的叶节点个数多)

    如果这个节点不是叶节点,那么显然,这个节点会有两种可能的状态:

    ①:这个节点状态为1,认为对应的情况为子树中状态为0的节点偏多,那么可以转移的dp部分是叶节点个数/2-叶节点个数

    ②:这个节点状态为0,认为对应的情况为子树中状态为1的节点偏多,那么可以转移的dp部分是0-叶节点个数/2-1

    那么我们分两类dfs处理,对每类情况分别合并即可

    如果这个节点是叶节点,那么我们反过来枚举他上面一条链的情况,然后统计代价即可,一定注意dp的第二维代表状态为0的节点的数目!

    在统计代价的时候,我们还涉及一个小问题,就是如果每次找到根节点时都枚举所有点计算代价,时间承受不了,所以我们对代价求前缀和,然后用类似倍增的方法计算总代价即可。

    当然,我们不可能一直枚举上面一条链的情况,所以我们直接把这条链的状态状压,传进dfs里即可,这样也就不涉及网上大部分题解都涉及到的压缩空间的问题了。

    提示:这是一棵满二叉树,所以可以用类似线段树的方式去遍历。

    这样这道题就结束了

    #include <cstdio>
    #include <cmath>
    #include <cstring>
    #include <cstdlib>
    #include <iostream>
    #include <algorithm>
    #include <queue>
    #include <stack>
    #define rt1 rt<<1
    #define rt2 (rt<<1)|1
    using namespace std;
    int dp[(1<<11)+5][(1<<11)+5];
    int v[(1<<11)+5][(1<<11)+5];
    int cv[(1<<11)+5];
    int ori[(1<<11)+5];
    int temp[(1<<11)+5];
    int lq[20],rq[20];
    int n;
    void dfs(int rt,int l,int r,int sit,int dep)
    {
        if(l==r)
        {
            dep--;
            dp[rt][0]=dp[rt][1]=0;
            if(ori[l])
            {
                dp[rt][1]=cv[l];
            }else
            {
                dp[rt][0]=cv[l];
            }
            for(int i=1;i<=dep;i++)
            {
                int mid=(lq[i]+rq[i])>>1;
                if((sit&(1<<(dep-i))))
                {
                    if(l<=mid)
                    {
                        dp[rt][0]+=v[l][rq[i]]-v[l][mid];
                    }else
                    {
                        dp[rt][0]+=v[l][mid]-v[l][lq[i]-1];
                    }
                }else
                {
                    if(l<=mid)
                    {
                        dp[rt][1]+=v[l][rq[i]]-v[l][mid];
                    }else
                    {
                        dp[rt][1]+=v[l][mid]-v[l][lq[i]-1];
                    }
                }
            }
            return;
        }
        int mid=(l+r)>>1;
        int len=r-l+1;
        lq[dep]=l;
        rq[dep]=r;
        dfs(rt1,l,mid,(sit<<1),dep+1);
        dfs(rt2,mid+1,r,(sit<<1),dep+1);
        memset(temp,0x3f,sizeof(temp));
        for(int i=0;i<=len/2-1;i++)
        {
            for(int j=0;j<=i;j++)
            {
                temp[i]=min(temp[i],dp[rt1][j]+dp[rt2][i-j]);
            }
        }
        for(int i=0;i<=len/2-1;i++)
        {
            dp[rt][i]=temp[i];
        }
        dfs(rt1,l,mid,(sit<<1)|1,dep+1);
        dfs(rt2,mid+1,r,(sit<<1)|1,dep+1);
        memset(temp,0x3f,sizeof(temp));
        for(int i=len/2;i<=len;i++)
        {
            for(int j=0;j<=i;j++)
            {
                temp[i]=min(temp[i],dp[rt1][j]+dp[rt2][i-j]);
            }
        }
        for(int i=len/2;i<=len;i++)
        {
            dp[rt][i]=temp[i];
        }
    }
    int main()
    {
        scanf("%d",&n);
        n=(1<<n);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&ori[i]);
        }
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&cv[i]);
        }
        for(int i=1;i<=n;i++)
        {
            for(int j=i+1;j<=n;j++)
            {
                scanf("%d",&v[i][j]);
                v[j][i]=v[i][j];
            }
        }
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=n;j++)
            {
                v[i][j]=v[i][j-1]+v[i][j];
            }
        }
        memset(dp,0x3f,sizeof(dp));
        dfs(1,1,n,0,1);
        int ans=2147483647;
        for(int i=0;i<=n;i++)
        {
            ans=min(ans,dp[1][i]);
        }
        printf("%d
    ",ans);
        return 0;
    }
  • 相关阅读:
    mysql 数据类型总结
    #微信小程序子传父 #小程序子组件向父组件传值 小程序子组件触发父组件中的事件
    #最近看到了一个写的很棒的系列文章专栏
    《MySQL45讲》读书笔记(四):索引
    《MySQL45讲》读书笔记(六):数据库事务概述
    《MySQL45讲》读书笔记(一):三大日志概述
    Java基础篇(05):函数式编程概念和应用
    数据采集组件:Flume基础用法和Kafka集成
    架构设计:数据服务系统0到1落地实现方案
    Java基础篇(04):日期与时间API用法详解
  • 原文地址:https://www.cnblogs.com/zhangleo/p/9873073.html
Copyright © 2011-2022 走看看