寒假训练 2(树形、数位dp)

只是一篇读书笔记而已:
树形dp,就是在树上做动态规划。树和子树具有递归性质,可以根据父与子结点的关系设计状态转移方程,用dfs从根结点开始进行记忆化搜索来求解。
例:anniversary party
不能同时选择相邻的父、子结点使选择出的结点总权值最大,那么整个状态集合就可以分为两种情况:1.选择该结点,那么它的子结点不能被选;2.不选择该结点,那么它的子结点可选也可以不选。
即 dp[father][1]+=dp[son][0]
dp[father][0]+=max(dp[son][1],dp[son][0])
根据题目要求,用邻接表建树。然后用查询的方法查到树的根节点,放到dfs里去进行记忆化搜索:

void dfs(int father)
{
    dp[father][0]=0;  //初始化父节点
    dp[father][1]=v[father];
    for(int i=0;i<tree[father].size();i++)
    {  //通过之前建立的邻接表递归父结点的每个子结点
        int son=tree[father][i];
        dfs(son);
        dp[father][0]=dp[father][0]+max(dp[son][1],dp[son][0]);
        dp[father][1]=dp[father][1]+dp[son][0];
    }
}

然后合并集合的两种情况 max(dp[father][0],dp[father][1])就可以得到最终的答案。
数位dp,就是对数字的位进行的与计数有关的dp
例: 求出[0,n]的范围内不包含4的数字个数(书上提到了两种方法)
1.递推
数位dp,需要我们对数字的位数进行分析,一位一位的递推。每个状态转移中只要让该位数的首数字不为4,递推下去就能得到该位数不包含4的数字的个数。

void init()
{
    dp[0][0]=1;
    for(i=1;i<=18;i++)    // 18位数不可能用暴力的方法
    {
        for(j=0;j<10;j++)
        {
            for(k=0;k<10;k++)
            {
                if(j!=4)
                    dp[i][j]=dp[i][j]+dp[i-1][k]; //i位数首字母不为4的状态
            }
        }
    }
}

但是这样得到的只是某个位数不包含4的数字个数,并不是一个给定的数字中不包含4的数字个数。比如说400,它的答案是首字母为0,1,2,3四个三位数不包含4的数字个数相加而得出的,所以我们还要设计一个函数将它们加起来。
首先我们先将给出的数字的每位数存到shu数组里,然后进行计算

int sl(int len)
{
    int ans=0;
    for(i=len;i>=1;i--)  //位数
    {
        for(j=0;j<shu[i];j++) //要加的数字首数字不能大于给出的数字在该位上数字的大小
        {
            if(j!=4)
                ans=ans+dp[i][j];//将之前求出的分块整合起来得到答案的总数
        }
        if(shu[i]==4)
        {
            ans--;break;//比如说343,出现4后后面的340,341,342就没了,要break掉,顺便要减掉边界343。
        }
    }
    return ans;
}

最后得出的答案ans还要+1,因为上述计算并不包括右边界,但是如果右边界有4也不用担心,因为之前在break前被减掉了。
2.记忆化搜索
只是把递推部分换成了dfs。记忆化搜索就是提前把dp[i]的值设为-1,后面搜索的时候如果发现dp[i]的值已经被更新过就可以直接返回,节省了继续向下搜索的步骤。
该题用记忆化搜索十分精妙,我们可以发现对于相同位数的数字中不含4的数字个数是相同的(10~19 9个 20~29 9个)因此对于相同位数只要搜索一次就够了,下次可以直接返回,大大降低了复杂度。

int dfs(int len,int ismax)
{
    int ans=0,t;
    if(!len) return 1; //设定边界,0位数的时候是1
    if(!ismax&&dp[len]!=-1) return dp[len]; //记忆化,直接返回
    t=(ismax?shu[len] : 9); //道理和递推的时候一样,比如说400,在我们搜索到三位数的时候我们的首数字只能取比4小的数字
    for(int i=0;i<=t;i++) //但是如果没有搜索到当前最大位数,那么它0-9都可以取
    {
        if(i==4) continue;
        ans=ans+dfs(len-1,ismax&&i==t);//判断是否搜索到当前的最大位数
    }
    if(!ismax) dp[len]=ans;//赋值,以便下一次直接返回
    return ans;
}

其实状压dp也看了,但是毛都没看懂,属实不太敢乱写…
状态压缩dp,当集合中方案太多时我们把方案用二进制数来表示。二进制操作有与、或、取反、移位等,通过这些来判断状态应该如何转移。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值