URAL 1057 Amount of Degrees 数位dp

原题链接:URAL 1057 Amount of Degrees

做了前两道数位dp,作者就在想:“数位dp原来这么简单啊,连状态都是一个模式,都是dp[i][j]表示以j开头的i位数字,看来再刷一道题就可以完全学会数位dp了。”

可是这道题目现场打脸,orz,一点思路都没有,用前两道类似的状态根本无法解决此题。直到看了大神的论文才知道数位dp的水原来这么深(可能只是本渣觉得深),以后还是老老实实刷题吧。

论文链接:算法合集之《浅谈数位类统计问题》

相信看了一遍之后就可以明白大概的意思了。不过读者可能对一个地方还是心存疑惑,那就是非二进制怎么处理?为什么论文中的方法是正确的呢?

先看下面这幅图


这是三进制的情况下的完全三叉树,显而易见,节点数字>1的都是不合理的,因为最后答案用3进制表示肯定都是0和1。

当我们把不合理的树“剪”了之后,发现剩下的树就是完全二叉树!

回顾一下论文中的处理方式:从高位到低位查找,碰到第一个非0、1的数字时,将该位数字及其右边的所有数字全部变成1,然后按照二进制的方法来处理。

比如三进制表示的数字021,按照上述方法就变成了011。可以看得出来这是最贴近021的合法数字,这样处理之后保证了符合条件的数字都小于等于011,所以这种处理是正确的。其实这么说可能还是有点抽象,仔细的看图理解一下说不定更容易懂。

代码如下:

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <cmath>

using namespace std;

typedef long long int ll;
int dp[35][35];
ll X, Y, K, B;

void init()
{
    memset(dp, 0, sizeof(dp));
    dp[0][0] = 1;
    for (int i = 1; i <= 31; i++)
    {
        dp[i][0] = 1;
        for (int j = 1; j <= i; j++)
            dp[i][j] = dp[i - 1][j] + dp[i - 1][j - 1];
    }
}

// 计算[0, n)中有多少个二进制含有K个1的数字
int cal(ll x)
{
    int ans = 0, tot = 0;
    for (int i = 31; i>= 1; i--)
    {
        if (x & (1 << i))
        {
            tot++;
            if (tot > K)
                break;
            x ^= (1 << i);
        }
        if ((1 << (i - 1)) <= x)
            ans += dp[i - 1][K - tot];
    }
    return ans;
}

// 将b进制的情况转化成2进制
ll change(ll x)
{
    int digit[35] = {0}, len = 0;
    while (x)
    {
        digit[++len] = x % B;
        x /= B;
    }
    int high = len;
    while (high >= 1 && digit[high] <= 1)
        high--;
    for (int i = high; i >= 1; i--)
        digit[i] = 1;
    ll res = 0;
    for (int i = len; i >= 1; i--)
        res = res * 2 + digit[i];
    return res;
}

int main()
{
    //freopen("test.txt", "r", stdin);

    init();
    while (~scanf("%lld%lld%lld%lld", &X, &Y, &K, &B))
    {
        ll x = change(X);
        ll y = change(Y);
        printf("%d\n", cal(y + 1) - cal(x));
    }
    return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值